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.303Feb 27 2024
paul at this website