I made a very simple benchmarking program that calculates all the prime numbers up to 10,000,000 in 4 different languages.
When I ran your code in python with a new algorithm:
real 0m3.583s
user 0m3.297s
sys 0m0.094s
Faster than the C benchmark you have above. I think that a simpler language helps you design better algorithms, but that is my opinion. (Also can use multiprocessing to make even faster)
def allPrimes(N):
is_prime = [1]*N
# We know 0 and 1 are composites
is_prime[0] = 0
is_prime[1] = 0
i = 2
# This will loop from 2 to int(sqrt(x))
while i*i <= N:
# If we already crossed out this number, then continue
if is_prime[i] == 0:
i += 1
continue
j = 2*i
while j < N:
# Cross out this as it is composite
is_prime[j] = 0
# j is incremented by i, because we want to cover all multiples of i
j += i
i += 1
return is_prime
print("total", sum(allPrimes(10000000)))
I spent a couple of days investigating the performance difference between JS/V8 and C, focusing first of all on the Hydrogen IR generated by the V8 engine. However, after making sure that no extraordinary optimizations are present there, I got back to the analysis of the assembly output and it struck me that the answer was a very simple one, boiling down to the couple of sentences in Jay Conrod's blog post on internals of V8:
According to the spec, all numbers in JavaScript are 64-bit floating point doubles. We frequently work with integers though, so V8 represents numbers with 31-bit signed integers whenever possible.
The example at hand allows fitting all computations in 32 bits and node.js takes full advantage of that! The C code utilizes the long
type, which on OP's (as well as my) platform happens to be a 64-bit type. Thus, it is a 32-bit arithmetic vs 64-bit arithmetic issue, mostly due to the expensive division/remainder operation.
If long
in the C code is replaced with int
, then the binary produced by gcc beats node.js.
Also, if the loop is made to look for primes over a range that is outside the realm of 32-bit numbers the performance of the node.js version drops significantly.
The used source code is found further in the answer, below the results.
Counting primes less than 10 million with C and node.js
$ gcc count_primes.c -std=c99 -O3 -lm -o count_primes_long
$ sed 's/long/int/g; s/%li/%i/g' count_primes.c > count_primes_int.c
$ gcc count_primes_int.c -std=c99 -O3 -lm -o count_primes_int
# Count primes <10M using C code with (64-bit) long type
$ time ./count_primes_long 0 10000000
The range [0, 10000000) contains 664579 primes
real 0m4.394s
user 0m4.392s
sys 0m0.000s
# Count primes <10M using C code with (32-bit) int type
$ time ./count_primes_int 0 10000000
The range [0, 10000000) contains 664579 primes
real 0m1.386s
user 0m1.384s
sys 0m0.000s
# Count primes <10M using node.js/V8 which transparently does the
# job utilizing 32-bit types
$ time nodejs ./count_primes.js 0 10000000
The range [ 0 , 10000000 ) contains 664579 primes
real 0m1.828s
user 0m1.820s
sys 0m0.004s
Performance figures in the vicinity of the limit of signed 32-bit integers
Counting the primes in the range of length 100,000 starting at the number contained in the first column:
| node.js | C (long)
-----------------------------------
2,000,000,000 | 0.293s | 0.639s # fully within the 32-bit range
-----------------------------------
2,147,383,647 | 0.296s | 0.655s # fully within the 32-bit range
-----------------------------------
2,147,453,647 | 2.498s | 0.646s # 50% within the 32-bit range
-----------------------------------
2,147,483,647 | 4.717s | 0.652s # fully outside the 32-bit range
-----------------------------------
3,000,000,000 | 5.449s | 0.755s # fully outside the 32-bit range
-----------------------------------
count_primes.js
"use strict";
var isPrime = function(n){
if (n < 2) {return false};
if (n === 2) {return true};
if (n === 3) {return true};
if (n % 2 === 0) {return false};
if (n % 3 === 0) {return false};
var sqrtOfN = Math.sqrt(n);
for (var i = 5; i <= sqrtOfN; i += 6){
if (n % i === 0) {return false}
if (n % (i + 2) === 0) {return false}
}
return true;
};
var countPrime = function(S, E){
var count = 0;
for (let i = S; i < E;i++){
if ( isPrime(i) ) { ++count; }
}
return count;
};
if( process.argv.length != 4) {
console.log('Usage: nodejs count_prime.js <range_start> <range_length>');
process.exit();
}
var S = parseInt(process.argv[2]);
var N = parseInt(process.argv[3]);
var E = S+N;
var P = countPrime(S, E);
console.log('The range [', S, ',', E, ') contains', P, 'primes');
count_primes.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define true 1
#define false 0
int isPrime (register long n){
if (n < 2) return false;
if (n == 2) return true;
if (n == 3) return true;
if (n % 2 == 0) return false;
if (n % 3 == 0) return false;
double sqrtOfN = sqrt(n);
for (long i = 5; i <= sqrtOfN; i += 6){
if (n % i == 0) return false;
if (n % (i + 2) == 0) return false;
}
return true;
};
int main(int argc, const char * argv[]) {
if ( argc != 3 ) {
fprintf(stderr, "Usage: count_primes <range_start> <range_length>\n");
exit(1);
}
const long S = atol(argv[1]);
const long N = atol(argv[2]);
register long count = 0;
for (register long i = S; i < S + N; i++){
if ( isPrime(i) ) ++count;
}
printf("The range [%li, %li) contains %li primes\n", S, S+N, count);
}