我做了一个非常简单的基准测试程序,该程序可以使用4种不同的语言计算出高达10,000,000的所有素数。
以上平均每次运行3次,用户时间
Node.js到目前为止运行最快。这使我感到困惑,原因有两个:
我使用-O2优化来编译c代码,但是我在所有优化级别上都进行了尝试,但是并没有明显的区别。
"use strict"; var isPrime = function(n){ //if (n !== parseInt(n,10)) {return false}; 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}; if (n % 1) {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(){ var count = 0; for (let i = 1; i < 10000000;i++){ if (isPrime(i)){ count++; } } console.log('total',count); }; countPrime();
$ time node primeCalc.js total 664579 real 0m2.965s user 0m2.928s sys 0m0.016s $ node --version v4.4.5
#include <stdio.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; if (n % 1) 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[]) { register long count = 0; for (register long i = 0; i < 10000000; i++){ if (isPrime(i)){ count++; } } printf("total %li\n",count); return 0; }
$ gcc primeCalc.c -lm -g -O2 -std=c99 -Wall $ time ./a.out total 664579 real 0m6.718s user 0m6.668s sys 0m0.008s
公共类PrimeCalc {
public static void main(String[] args) { long count = 0; for (long i = 0; i < 10000000; i++){ if (isPrime(i)){ count++; } } System.out.println("total "+count); } public static boolean isPrime(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; if (n % 1 > 0) return false; double sqrtOfN = Math.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; }; }
$ javac PrimeCalc.java $ time java PrimeCalc total 664579 real 0m7.197s user 0m7.036s sys 0m0.040s $ java -version java version "1.7.0_111" OpenJDK Runtime Environment (IcedTea 2.6.7) (7u111-2.6.7-0ubuntu0.14.04.3) OpenJDK 64-Bit Server VM (build 24.111-b01, mixed mode)
import math def isPrime (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 if n % 1 >0 : return False sqrtOfN = int(math.sqrt(n)) + 1 for i in xrange (5, sqrtOfN, 6): if n % i == 0 : return False; if n % (i + 2) == 0 : return False; return True count = 0; for i in xrange(10000000) : if isPrime(i) : count+=1 print "total ",count
time python primeCalc.py total 664579 real 0m46.588s user 0m45.732s sys 0m0.156s $ python --version Python 2.7.6
$ uname -a Linux hoarfrost-node_6-3667558 4.2.0-c9 #1 SMP Wed Sep 30 16:14:37 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
(6.66 s)优化2,gcc primeCalc.c -lm -O2 -std = c99 -Wall
这段代码使用的示例实际上并没有做任何重要的事情。好像编译器可以在编译时算出结果,甚至不需要执行100000000次循环就可以得出答案。如果将另一除法步骤添加到计算中,则优化的意义将大大降低。
for (long i = 0; i < 100000000; i++) { d += i >> 1; d = d / (i +1); // <-- New Term }
更新 03/15/2017从@leon阅读答案后,我进行了一些验证测试。
测试1至32位Beaglebone Black,664,579装填至10,000,000
未经编辑的calcPrime.js和calcPrime.c在具有32位处理器的Beaglebone黑色上运行。
测试2-64位Macbook Pro,664,579灌注10,000,000
用uint32_t替换calcPrime.c代码中的长数据类型,并在具有64位处理器的MacBook Pro上运行。
测试3-64位Macbook Pro,91,836个素数(i = 1; i <8,000,000,000; i + = 10000)
在C代码中使用无符号的长数据类型,强制javascript使用64位。-C代码= 20.4秒(clang,长数据类型)-JS代码= 17.8秒(节点v4)
测试4-64位Macbook Pro,86,277个素数(i = 8,000,00,001; i <16,000,000,000; i + = 10000)
在C代码中使用无符号的长数据类型,强制javascript使用所有64位。-C代码= 35.8秒(c,长数据类型)-JS代码= 34.1秒(节点v4)
测试5-Cloud9 64位Linux,(i = 0; i <10000000; i ++)
language datatype time % to C javascript auto 3.22 31% C long 7.95 224% C int 2.46 0% Java long 8.08 229% Java int 2.15 -12% Python auto 48.43 1872% Pypy auto 9.51 287%
测试6-Cloud9 64位Linux,(i = 8000000001; i <16000000000; i + = 10000)
javascript auto 52.38 12% C long 46.80 0% Java long 49.70 6% Python auto 268.47 474% Pypy auto 56.55 21%
(所有结果均为三个运行的平均用户秒数,运行之间的时间差异<10%)
混合结果
在整数范围内将C和Java数据类型更改为整数会大大加快执行速度。在BBB和Cloud9计算机上,切换到int使得C比node.js更快。但是在我的Mac上,node.js程序仍然运行得更快。也许是因为Mac使用的是clang,而BBB和Cloud 9计算机使用的是gcc。有谁知道gcc的编译程序是否比gcc快?
当使用所有64位整数时,C在BBB和Cloud9 PC上比node.js快一点,但在我的MAC上慢一点。
在这些测试中,您还可以看到pypy的速度大约是标准python的四倍。
node.js甚至与C兼容的事实令我惊讶。
我花了几天的时间研究JS / V8和C之间的性能差异,首先关注V8引擎生成的Hydrogen IR。但是,在确保在那里不存在任何非凡的优化之后,我回到了对汇编输出的分析,这让我感到惊讶,答案是一个非常简单的答案,精简到杰伊·康罗德(Jay Conrod)关于内部的博客文章中的几句话。 V8:
根据规范,JavaScript中的所有数字均为64位浮点双精度。不过,我们经常使用整数,因此 V8尽可能用31位带符号整数表示数字 。
当前的示例允许将所有计算拟合为32位,node.js充分利用了这一点!C代码使用该long类型,该类型在OP(以及我的平台)上恰好是64位类型。因此,这是32位算术与64位算术问题,主要是由于昂贵的除法/余数运算。
long
如果long在C代码中将替换为int,则gcc生成的二进制文件将击败node.js。
int
另外,如果循环查找超出32位数字范围的范围内的质数,则node.js版本的性能会大大下降。
所使用的源代码可在结果的下方答案中找到。
使用C和node.js计数少于1000万的素数
$ 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
性能数字接近带符号的32位整数的限制
从第一列中的数字开始计算长度为100,000的素数:
| 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); }