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:
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)
# 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
#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
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
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)
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)
<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
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
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
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
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)
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
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)
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});
}