An honest memory management benchmark

Published on Feb 27, 2025 in Benchmarks  

In a previous article, I had already done a benchmark of languages oriented on computing power. As explained in the article, this approach is interesting, but also biased.

So, here’s a benchmark based on memory management.

This time, I limited this test to the fastest languages from the previous benchmark.

I also added C++. In terms of pure computing power, the result would have been the same as for C. But for memory management, I found it interesting to integrate this language.

I also dropped Zig, because I noticed during a first test with page_allocator that it’s extremely slow. I don’t know Zig very well and it’s a real chaos with the continuous breaking changes from version to version to find the right documentation. I didn’t want to spend too much time on a language that is still little used.

I made the effort to test Python because it’s a widely used language. But I didn’t include it in this review because it will make the results less readable. The result is even more catastrophic for memory management. Python is 100 times slower than C, and the JIT compiler doesn’t change anything.

I used the same conditions and versions as for the previous test.

Here are the results.

Benchmark result

This time, no big surprise.

I just thought that LuaJIT had better memory management.

Oddly, V8’s memory management is better in Chromium than in Node.

Unsurprisingly, SpiderMonkey (Firefox) is the worst. I was surprised that it was slightly better than V8 in terms of pure computing power. But in terms of memory, SpiderMonkey is true to his reputation.

Of course, comparing languages in non-concrete cases is quite complicated. And there’s not only computational speed and memory management to consider. You also have to take into account the memory footprint and ease of use… But with these two benchmarks, you get an idea of how well these languages perform.

It should also be noted that some languages are less suitable in certain contexts.

Taking these results literally, one could say that all programs should be written in C. But C is not memory safe and it’s not a good idea to use it on a server, except on large open source projects because the code is monitored by many security experts.

Rust seems like a better option, but the language is harder to learn and still has few experts who aren’t afraid of the big bad borrow checker. Even though it is slower, Golang is usually a better option because it’s easy to learn.

Python should only be used for simple scripts that don’t require high performance or for proof of concept. It has many libs and it’s sometimes convenient to use it. But if performance is important, it should always be used in a separate service, so that you can scale it easily. When most of the processing happens on CUDA, for example with Pytorch, Python remains a good option, for lack of a better one.

Note: If you don’t use the allocated memory, compilers tend to optimize this and not allocate anything. That’s why I go through memory to put values into it. In each iteration, I add the value of the first element of the array and display the total (zero) at the end. I needed this for Rust, otherwise, the compiler optimizes the code too much and the function is not called. So I added it to all the programs for reasons of fairness.

Here’s the code I used:

C

c
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

void benchmark_memory_allocation(size_t num_iterations) {
    int total = 0;
    for (size_t i = 0; i < num_iterations; i++) {
        int *arr = (int *)malloc(1000 * sizeof(int));
        if (arr == NULL) {
            fprintf(stderr, "Allocation error\n");
            exit(EXIT_FAILURE);
        }
        for (size_t j = 0; j < 1000; j++) {
            arr[j] = j;
        }
        total += arr[0];
        free(arr);
    }
    printf("Total: %d\n", total);
}

void main() {
    struct timeval start_tv, end_tv;
    double start, end;
    int num_iterations = 1000000;
    gettimeofday(&start_tv, NULL);
    start = start_tv.tv_sec * 1000.0 + start_tv.tv_usec / 1000.0;
    benchmark_memory_allocation(num_iterations);
    gettimeofday(&end_tv, NULL);
    end = end_tv.tv_sec * 1000.0 + end_tv.tv_usec / 1000.0;
    printf("C Allocation Time: %f ms\n", end - start);
}

C++

cpp
#include <iostream>
#include <vector>
#include <chrono>

void benchmark_memory_allocation(size_t num_iterations) {
    int total = 0;
    for (size_t i = 0; i < num_iterations; i++) {
        std::vector<int> arr(1000);
        for (size_t j = 0; j < 1000; j++) {
            arr[j] = j;
        }
        total += arr[0];
    }
    std::cout << "Total: " << total << std::endl;
}

int main() {
    int num_iterations = 1000000;
    auto start = std::chrono::high_resolution_clock::now();
    benchmark_memory_allocation(num_iterations);
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> elapsed = end - start;
    std::cout << "C++ Allocation Time: " << elapsed.count() << " ms" << std::endl;
}

Golang

go
package main

import (
	"fmt"
	"time"
)

func benchmarkMemoryAllocation(numIterations int) {
	var total int
	for i := 0; i < numIterations; i++ {
		arr := make([]int, 1000)
		for j := range arr {
			arr[j] = j
		}
		total += arr[0]
	}
	fmt.Printf("Total: %d\n", total)
}

func main() {
	var start time.Time
	var elapsed time.Duration
	numIterations := 1000000
	start = time.Now()
	benchmarkMemoryAllocation(numIterations)
	elapsed = time.Since(start)
	fmt.Printf("Go Allocation Time: %f ms\n", elapsed.Seconds()*1000)
}

Java

java
public class MemoryAllocationBenchmark {
    public static void benchmarkMemoryAllocation(int numIterations) {
        int total = 0;
        for (int i = 0; i < numIterations; i++) {
            int[] arr = new int[1000];
            for (int j = 0; j < 1000; j++) {
                arr[j] = j;
            }
            total += arr[0];
        }
        System.out.println("Total: " + total);
    }

    public static void main(String[] args) {
        int numIterations = 1000000;
        long startTime = System.currentTimeMillis();
        benchmarkMemoryAllocation(numIterations);
        long endTime = System.currentTimeMillis();
        System.out.println("JavaScript Allocation Time: " + (endTime - startTime) + " ms");
    }
}

JavaScript (Node version)

javascript
const { performance } = require('perf_hooks');

function benchmarkMemoryAllocation(numIterations) {
    let total = 0;
    for (let i = 0; i < numIterations; i++) {
        let arr = new Array(1000);
        for (let j = 0; j < 1000; j++) {
            arr[j] = j;
        }
        total += arr[0];
        arr = null;
    }
    console.log("Total: " + total);
}

const numIterations = 1000000;
const startTime = performance.now();
benchmarkMemoryAllocation(numIterations);
const endTime = performance.now();
console.log("JavaScript Allocation Time: " + (endTime - startTime) + " ms");

JavaScript (Browser version)

html
<script>
function benchmarkMemoryAllocation(numIterations) {
    let total = 0;
    for (let i = 0; i < numIterations; i++) {
        let arr = new Array(1000);
        for (let j = 0; j < 1000; j++) {
            arr[j] = j;
        }
        total += arr[0];
        arr = null;
    }
    console.log("Total: " + total);
}

const numIterations = 1000000;
const startTime = performance.now();
benchmarkMemoryAllocation(numIterations);
const endTime = performance.now();
console.log("JavaScript Allocation Time: " + (endTime - startTime) + " ms");
</script>

Lua

lua
function benchmark_memory_allocation(num_iterations)
    local total = 0
    for i = 1, num_iterations do
        local arr = {}
        for j = 1, 1000 do
            arr[j] = j
        end
        total = total + arr[1]
    end
    print("Total: " .. total)
end

local start_time = os.clock()
local num_iterations = 1000000
benchmark_memory_allocation(num_iterations)
local end_time = os.clock()
local elapsed_time = end_time - start_time
print(string.format("Lua Allocation Time: %f ms", elapsed_time * 1000))

PHP

php
<?php

function benchmark_memory_allocation($num_iterations) {
    $total = 0;
    for ($i = 0; $i < $num_iterations; $i++) {
        $arr = array_fill(0, 1000, 0);
        if ($arr === false) {
            echo "Allocation error\n";
            exit(1);
        }
        for ($j = 0; $j < 1000; $j++) {
            $arr[$j] = $j;
        }
        $total += $arr[0];
    }
    echo "Total: $total\n";
}

$iterations = 1000000;
$time_start = microtime(true);
benchmark_memory_allocation($iterations);
$time_end = microtime(true);
$elapsed_time = $time_end - $time_start;
echo("PHP Allocation Time: ".$elapsed_time * 1000 ."\n");

Rust

rust
use std::time::Instant;

fn benchmark_memory_allocation(num_iterations: usize) {
    let mut total = 0;
    for _ in 0..num_iterations {
        let mut arr = vec![0; 1000];
        for j in 0..arr.len() {
            arr[j] = j;
        }
        total += arr[0];
    }
    println!("Total: {}", total);
}

fn main() {
    let num_iterations = 1000000;
    let start = Instant::now();
    benchmark_memory_allocation(num_iterations);
    let elapsed = start.elapsed();
    let elapsed_millis = elapsed.as_secs_f64() * 1000.0;
    println!("Rust Allocation Time: {:.6} ms", elapsed_millis);
}