An honest language benchmark

Published on Feb 1, 2025 in Benchmarks  

On the internet, you can regularly find language benchmarks that seem to me to be more based on ideology than on a real test.

In these benchmarks, we find C and Rust in the lead. So far, nothing very shocking.

Then you usually find Java much slower. Nothing very shocking either.

Then Golang is twice as slow as Java. Now I’m starting to have doubts. How could a compiled language be twice as slow as a bytecode language?

Also, still in these benchmarks, Python is often presented as being much faster than PHP. However, PHP has become very fast since its version 7 and Python is stagnating in its slowness.

Of course, none of these so-called benchmarks share the code they used. So I decided to do my own test and share my code with you!

For this benchmark, I used a program that counts prime numbers smaller than 1000000.

I used a naïve algorithm, because performance is not the subject. The purpose of this benchmark is to compare languages with the same algorithm.

This choice does not test the performance of all language features, especially memory management. But it allows you to have an idea of the pure performance (loops + computation) of a language.

I chose commonly used languages. So I tested C, Golang, Java, Javascript, PHP, Python, Ruby and Rust.

For JavaScript, I tested several engines. V8 backend (Node) and frontend (Chrome-like) and SpiderMonkey (Firefox).

I also added other languages that are less used for different reasons.

  • Fortran 77 which is an old school language renowned for its performance.
  • Zig, which is still little used, but is also renowned for its performance.
  • And also Lua, because it’s a language that I use as part of a mission, and I wanted to compare classic Lua (interpreted) and Luajit (with a just-in-time compilation).

This benchmark was done under Debian 12, with an N100 CPU. I ran each program 10 times and averaged the results.

Here are the versions I used:

  • C gcc 12.2.0
  • Fortran gfortran 12.2.0
  • Golang 1.22.5
  • Java openjdk 17.0.13
  • JavaScript with Node v20.16.0
  • JavaScript with Chromium 132.0.6834.83
  • JavaScript with Firefox 128.6.0esr
  • Lua 5.4.4
  • Luajit 2.1.0-beta3
  • PHP 8.2.26
  • Python 3.11.2
  • Python 3.13.0 for JIT
  • Ruby 3.1.2p20
  • Rust 1.79.0
  • Zig 0.6.0

And, here are the results:

Benchmark result

For me, the big winner of this test is JavaScript. I didn’t think that the JIT compilers of modern JavaScript engines were so fast. Of course, if there was memory management, JavaScript wouldn’t have been so high in the rankings. But as explained above, this is part of the biases of this benchmark. I’m preparing another benchmark with memory management.

And the big loser is Fortran 77, but I just believe that modern compilers don’t make the effort to properly optimize this language.

Not really a surprise for me that Python is 20 times slower than most current languages. Python JIT is experimental and doesn’t make much of a difference on the code being tested. I just wasted my time compiling a version of Python to test this feature, but I wanted to be fair and test this option.

Here’s the code I used:

Build commands (for Golang, I tried various optimization options but apart from slowing down the build, I didn’t see any difference)

terminal

# c
gcc -O3 -o prime prime.c

# golang
go build prime.go

# java
javac PrimeCalculator.java

# php (jit configuration)
php -i | grep -E '^opcache.(enable_cli|jit|jit_buffer_size) '
opcache.enable_cli => On => On
opcache.jit => tracing => tracing
opcache.jit_buffer_size => 50M => 50M

# python (compile options to enable jit)
./configure --prefix=$HOME/py3.13 --enable-experimental-jit --enable-optimizations

# rust
rustc -C opt-level=3  prime.rs

# f77
gfortran -O3 -std=legacy prime.f -o prime

# zig
zig run prime.zig --release-fast

C

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

bool is_prime(int number) {
    int i, max;

    if (number <= 1) {
        return false;
    }
    for (i = 2; i * i <= number; i++) {
        if (number % i == 0) {
            return false;
        }
    }
    return true;
}

void main() {
    int i, num_prime = 0;
    struct timeval start_tv, end_tv;
    double start, end;

    gettimeofday(&start_tv, NULL);
    start = start_tv.tv_sec * 1000.0 + start_tv.tv_usec / 1000.0;
    for (i = 2; i <= 1000000; i++) {
        if (is_prime(i)) {
            num_prime++;
        }
    }
    gettimeofday(&end_tv, NULL);
    end = end_tv.tv_sec * 1000.0 + end_tv.tv_usec / 1000.0;
    printf("%d\n", num_prime);
    printf("%f\n", end - start);
}

Golang

golang
package main

import (
	"fmt"
	"time"
)

func isPrime(number int) bool {
	if number <= 1 {
		return false
	}
	for i := 2; i*i <= number; i++ {
		if number%i == 0 {
			return false
		}
	}
	return true
}

func main() {
	var start time.Time
	var elapsed time.Duration
	var i, numPrime int

	start = time.Now()
	numPrime = 0
	for i = 2; i <= 1000000; i++ {
		if isPrime(i) {
			numPrime++
		}
	}
	elapsed = time.Since(start)
	fmt.Printf("%d\n%f\n", numPrime, elapsed.Seconds()*1000)
}

Java

java
public class PrimeCalculator {
    public static boolean isPrime(int number) {
        if (number <= 1) {
            return false;
        }
        for (int i = 2; i * i <= number; i++) {
            if (number % i == 0) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        int numPrime = 0;

        for (int i = 2; i <= 1000000; i++) {
            if (isPrime(i)) {
                numPrime++;
            }
        }
        System.out.println(numPrime);
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }
}

JavaScript (Node version)

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

function isPrime(number) {
    if (number <= 1) {
        return false;
    }
    for (let i = 2; i * i <= number; i++) {
        if (number % i === 0) {
            return false;
        }
    }
    return true;
}

const startTime = performance.now();
let numPrime = 0;

for (let i = 2; i <= 1000000; i++) {
    if (isPrime(i)) {
        numPrime++;
    }
}

const endTime = performance.now();
console.log(numPrime);
console.log(endTime - startTime);

JavaScript (Browser version)

html
<script>
function isPrime(number) {
    if (number <= 1) {
        return false;
    }
    for (let i = 2; i * i <= number; i++) {
        if (number % i === 0) {
            return false;
        }
    }
    return true;
}

const startTime = performance.now();
let numPrime = 0;

for (let i = 2; i <= 1000000; i++) {
    if (isPrime(i)) {
        numPrime++;
    }
}

const endTime = performance.now();
console.log(numPrime);
console.log(endTime - startTime);
</script>

PHP

php
<?php

function is_prime($number) {
	if ($number <= 1) {
		return 0;
	}
	$i = 2;
	while ($i * $i <= $number) {
		if ($number % $i == 0) {
			return false;
		}
		$i++;
	}
	return true;
}

$time_start = microtime(true);
$num_prime = 0;

for ($i = 2; $i <= 1000000; $i++) {
	if (is_prime($i)) {
		$num_prime++;
	}
}

$time_end = microtime(true);
$elapsed_time = $time_end - $time_start;
echo($num_prime ."\n");
echo($elapsed_time * 1000 ."\n");

Python

python
from time import time

def is_prime(number):
    if number <= 1:
        return False
    i = 2
    while i * i <= number:
        if number % i == 0:
            return False
        i += 1
    return True

t = time()
num_prime = 0
for n in range(2, 1000001):
    if is_prime(n):
        num_prime += 1
print(num_prime)
print((time()-t)*1000)

Ruby

ruby
def prime?(number)
  return false if number <= 1
  (2..number).each do |i|
    break if i * i > number
    return false if number % i == 0
  end
  true
end

start_time = Time.now
num_prime = 0
(2...1_000_000).each do |i|
  if prime?(i)
    num_prime += 1
  end
end
end_time = Time.now
elapsed_time = (end_time - start_time) * 1000 
puts "#{num_prime}"
puts "#{elapsed_time}"

Rust

rust
use std::time::Instant;

fn is_prime(number: u32) -> bool {
    if number <= 1 {
        return false;
    }
    for i in 2..=number {
        if i * i > number {
            break;
        }
        if number % i == 0 {
            return false;
        }
    }
    true
}

fn main() {
    let start = Instant::now();
    let mut num_prime = 0;

    for i in 2..1_000_000 {
        if is_prime(i) {
            num_prime += 1;
        }
    }
    let elapsed = start.elapsed();
    let elapsed_millis = elapsed.as_secs_f64() * 1000.0;
    println!("{}", num_prime);
    println!("{:.6}", elapsed_millis);
}

Fortran 77 (my highlighter does not handle column 7 for the first line, so you’ll have to put the line correctly if you want to run this code)

fortran
PROGRAM Prime
      INTEGER I, NUMBER, NUM_PRIME
      REAL START_TIME, END_TIME
      LOGICAL IS_PRIME

      CALL CPU_TIME(START_TIME)
      NUM_PRIME = 0
      DO 10 NUMBER = 2, 1000000
          IS_PRIME = .TRUE.
          I = 2
          DO 20 WHILE (I * I <= NUMBER)
              IF (MOD(NUMBER, I) .EQ. 0) THEN
                  IS_PRIME = .FALSE.
              END IF
              I = I + 1
20        CONTINUE
          IF (IS_PRIME) THEN
              NUM_PRIME = NUM_PRIME + 1
          END IF
10    CONTINUE
      CALL CPU_TIME(END_TIME)
      PRINT *, NUM_PRIME
      PRINT *, (END_TIME - START_TIME) * 1000

      END PROGRAM Prime

Lua

lua
local function is_prime(number)
    if number <= 1 then
        return false
    end
    for i = 2, number do
        if i * i > number then
            break
        end
        if number % i == 0 then
            return false
        end
    end
    return true
end

local start_time = os.clock()
local num_prime = 0
for i = 2, 1000000 do
    if is_prime(i) then
        num_prime = num_prime + 1
    end
end
local end_time = os.clock()
local elapsed_time = end_time - start_time
print(string.format("%d\n%f", num_prime, elapsed_time * 1000))

Zig (my highlighter does not support Zig)

plaintext
const std = @import("std");
const Timer = std.time.Timer;
const stdout = std.io.getStdOut().outStream();

fn isPrime(number: i32) bool {
    if (number <= 1) {
        return false;
    }
    var i: i32 = 2;
    while (i * i <= number) {
        if (@mod(number, i) == 0) {
            return false;
        }
        i += 1;
    }
    return true;
}

pub fn main() !void {
    var timer = try std.time.Timer.start();
    var i: i32 = 2;
    var num_prime: i32 = 0;

    while (i <= 1_000_000) : (i += 1) {
        if (isPrime(i)) {
            num_prime += 1;
        }
    }
    const elapsed = timer.read();
    try stdout.print("{}\n", .{num_prime});
    try stdout.print("{d:.6}\n", .{@intToFloat(f64, elapsed) / 1_000_000.0});
}