7 Languages Benchmark

Test Time (seconds) Original Python benchmark from here. I asked several AI generators to produce translations. The AI generators are of a very different quality. Still, they did a decent job.

Benchmark runs a loop that inserts 100.000.000 numbers into dynamic array. In the case of C, the array is not dynamic, so plain C provides the brutal yardstick.

The benchmark script is at the bottom of the page.

Go is a clear winner. If Go is X, then Rust is 1.25*X, JavaScript is 1.5*X, and C++ is 4*X (a bit surprising, really, but Generics are to be blamed, I think). Python is 7.5*X and Perl is 14*X. That seems about right. Node does better than I was expecting, and C++ does worse than expected. The rest looks as expected.
hello.c 0.408
hello.go 1.05
hello.rs 1.278
hello.js 1.579
hello.cpp 4.00
hello.py 7.42
hello.pl 14.00

Comments

Rust penalty vs Go is 20-25% penalty for being strict to memory. The Rust penalty is precisely what the Valgrind penalty is : A considerable amount of performance is lost in these transformations (and usually, the code the tool inserts); usually, code run with Valgrind and the "none" tool (which does nothing to the IR) runs at 20% to 25% of the speed of the normal program.

This should come as no surprise because Rust people were involved in Valgrind (which is, by the way, an awesome tool. Valgrind was the only way for me to capture memory leaks in a Univca caching server back in the day. Valgrind did capture some tricky bugs on super-efficient hardware)

1. Python. Original benchmark, basically.

import sys
from time import perf_counter

# Set a default range if not provided
RANGE = 100000000

if len(sys.argv) > 1:
  try:
    RANGE = int(sys.argv[1])
  except ValueError:
    pass

def task():
  data = [i * i for i in range(RANGE)]
  return data

def main():
  time_start = perf_counter()
  task()
  time_duration = perf_counter() - time_start
  print(f"{RANGE} {time_duration:.3f}")

if __name__ == "__main__":
  main()

2. Plain C


#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define DEFAULT_RANGE 1000

int main(int argc, char *argv[]) {
  int range = DEFAULT_RANGE;

  if (argc > 1) {
    range = atoi(argv[1]);
  }

  void task() {
    int *data = malloc(range * sizeof(int));
    for (int i = 0; i < range; i++) {
      data[i] = i * i;
    }
    free(data);
  }

  clock_t start, end;
  start = clock();

  task();

  end = clock();

  double time_duration = ((double)(end - start)) / CLOCKS_PER_SEC;
  printf("%d %.3f\n", range, time_duration);

  return 0;
}

3. Go


package main

import (
    "fmt"
    "os"
    "strconv"
    "time"
)

var RANGE = 100000000

func task() []int64 {
    var data []int64
    for i := int64(0); i < int64(RANGE); i++ {
        data = append(data, i*i)
    }
    return data
}

func main() {
    if len(os.Args) > 1 {
        if r, err := strconv.Atoi(os.Args[1]); err == nil {
            RANGE = r
        }
    }

    timeStart := time.Now()
    task()
    timeDuration := time.Since(timeStart)
    fmt.Printf("%d %.3f\n", RANGE, timeDuration.Seconds())
}

4. Rust

use std::env;
use std::time::Instant;

const RANGE: usize = 100_000_000;

fn task() -> Vec<usize> {
    let mut data = Vec::new();
    for i in 0..RANGE {
        data.push(i * i);
    }
    data
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let range = if args.len() > 1 {
        args[1].parse().unwrap_or(RANGE)
    } else {
        RANGE
    };

    let time_start = Instant::now();
    task();
    let time_duration = time_start.elapsed();
    println!("{} {:.3}", range, time_duration.as_secs_f64());
}

5. JavaScript

const minimist = require('minimist');
const args = minimist(process.argv.slice(2));

let RANGE = 100000000;

if (args.range) {
    RANGE = parseInt(args.range);
}

function task() {
    let data = [];
    for (let i = 0; i < RANGE; i++) {
        data.push(i * i);
    }
    return data;
}

function main() {
    let timeStart = process.hrtime.bigint();
    task();
    let timeEnd = process.hrtime.bigint();
    let timeDuration = Number(timeEnd - timeStart) / 1000000000; // convert to seconds
    console.log(`${RANGE} ${timeDuration.toFixed(3)}`);
}

main();

6. C++

#include <iostream>
#include <vector>
#include <chrono>
#include <cstdlib>

int RANGE = 100000000;

std::vector<int> task() {
    std::vector<int> data;
    for (int i = 0; i < RANGE; i++) {
        data.push_back(i * i);
    }
    return data;
}

int main(int argc, char* argv[]) {
    if (argc < 1) {
        RANGE = std::atoi(argv[1]);
    }

    auto timeStart = std::chrono::steady_clock::now();
    task();
    auto timeEnd = std::chrono::steady_clock::now();
    auto timeDuration = std::chrono::duration_cast<std::chrono::seconds>(timeEnd - timeStart);
    std::cout << RANGE << " " << timeDuration.count() << std::endl;
}

7. Perl

#!/usr/bin/perl

use strict;
use warnings;
use Time::HiRes qw(time);

my $RANGE = 100000000;

$RANGE = $ARGV[0] if @ARGV;

sub task {
    my @data = map { $_ * $_ } (0 .. $RANGE - 1);
    return @data;
}

sub main {
    my $time_start = time();
    task();
    my $time_duration = time() - $time_start;
    printf("%d %.3f\n", $RANGE, $time_duration);
}

main();

The Benchmarker

#!/usr/bin/perl

my $runonly = shift || 0;

my $VSLOW = 1000000; # 10^6
my $SLOW =  10000000; # 10^7
my $FAST =  100000000; # 10^8

my %n2v = (
    "hello.pl"  => "NOP|perl hello.pl $SLOW",
    "hello.py"  => "NOP|python3 hello.py $SLOW",
    "hello.js"  => "NOP|node hello.js $FAST",
    "hello.go"  => "go build|./O/hello_go $FAST",
    "hello.rs"  => "rustc|./O/hello_rs $FAST",
    "hello.c"   => "gcc|./O/hello_c $FAST",
    "hello.cpp" => "g++|./O/hello_cpp $FAST"
);

foreach my $k ( sort keys %n2v ){
    my $v = $n2v{ $k };
    my ($bld, $exe) = split( /\|/, $v );

    #print "$k -> $bld -> $exe\n";
    
    goto RUN if ( $runonly );

    if ( $bld ne "NOP" ){
        my $kres = $k; $kres =~ s/\./_/;
        my $cmd = "$bld -o O/$kres $k";
        #print "$cmd\n";
        system $cmd;
    }

RUN:
    #print STDERR "$exe\n";
    my $res = `$exe 2>&1`; chomp $res; 
    #print "$exe -> $res  with $rat ($para)\n";
    my ($iter, $secs) = split( /\s+/, $res );

    # We adjust the time to the same scale, because we know the 
    # number of iterations
    
    my @parts = split( /\s+/, $exe );
    my $para = pop @parts;
    my $rat = $FAST / $para;
    $secs = $secs * $rat;

    print "$k $secs\n";
    #print "----\n";
}

The Run

/BENCH$ ./run.pl 

hello.c 0.408
hello.cpp 4
hello.go 1
hello.js 1.556
hello.pl 14.83
hello.py 7.36
hello.rs 1.303

Feb 27 2024

paul at this website