node.js如何比c和java更快? 基准比较node.js,c,java和python

我做了一个非常简单的基准testing程序,可以用4种不同的语言计算所有素数高达10,000,000的素数。

  • (2.97秒) – node.js(javascript)(4.4.5)
  • (6.96秒) – c(c99)
  • (6.91秒) – java(1.7)
  • (45.5秒) – python(2.7)

以上是每次运行3次,用户时间的平均值

Node.js运行得最快。 这令我感到困惑的原因有两个:

  1. JavaScript总是使用双精度浮点数variables,而c和java在这种情况下使用(长)整数。 整数math应该更快。
  2. 当实际上它是一种及时编译语言时,JavaScript通常被称为解释。 但即使如此,JIT编译器如何比完全编译的语言更快呢? python代码运行速度最慢,这并不奇怪,但是为什么node.js代码运行速度与python差不多?

我使用-O2优化编译了c代码,但是我尝试了所有优化级别,并没有发现明显的差异。

countPrime.js

"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(); 

node.js结果

 $ time node primeCalc.js total 664579 real 0m2.965s user 0m2.928s sys 0m0.016s $ node --version v4.4.5 

primeCalc.c

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

c结果

 $ gcc primeCalc.c -lm -g -O2 -std=c99 -Wall $ time ./a.out total 664579 real 0m6.718s user 0m6.668s sys 0m0.008s 

PrimeCalc.java

公共类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; }; } 

java的结果

  $ 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) 

primeCalc.py

 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 

python的结果

 time python primeCalc.py total 664579 real 0m46.588s user 0m45.732s sys 0m0.156s $ python --version Python 2.7.6 

Linux的

 $ 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 

额外的运行时间(附录)

  • (7.81秒)没有优化,gcc primeCalc.c -lm -std = c99 -Wall
  • (8.13秒)优化0,gcc primeCalc.c -lm -O0 -std = c99 -Wall
  • (7.30秒)优化1,gcc primeCalc.c -lm -O1 -std = c99 -Wall
  • (6.66秒)优化2,gcc primeCalc.c -lm -O2 -std = c99 -Wall

    • 每次优化级别用户时间平均3次新运行*

我在这里阅读这篇文章: 为什么这个NodeJS 2x比本地C更快? 这段代码使用了一个实际上并不重要的例子。 就好像编译器可以在编译时计算出结果,甚至不需要循环执行循环100万次就可以得出答案。 如果在计算中增加另一个除法步骤,优化就不那么重要了。

 for (long i = 0; i < 100000000; i++) { d += i >> 1; d = d / (i +1); // <-- New Term } 
  • (1.88秒)没有优化
  • (1.53秒)优化(-O2)

更新 03/15/2017在阅读@leon的答案后,我进行了一些validationtesting。

testing1 – 32位Beaglebone黑色,664,579素数高达10,000,000

未经编辑的calcPrime.js和calcPrime.c运行在具有32位处理器的Beaglebone黑色上。

  • C代码= 62秒(gcc,长数据types)
  • JS代码= 102秒(节点v4)

testing2 – 64位Macbook Pro,664,579素数高达10,000,000

将calcPrime.c代码中的长数据typesreplace为uint32_t,并在具有64位处理器的MacBook Pro上运行。

  • C代码= 5.73秒(铛,长数据types)
  • C代码= 2.43秒(clang,uint_32_t数据types)
  • JS代码= 2.12秒(节点v4)

testing3 – 64位Macbook Pro,91,836素数(i = 1; i <8,000,000,000; i + = 10000)

在C代码中使用无符号的长数据types,强制JavaScript使用一些64位。 – C代码= 20.4秒(铛,长数据types) – JS代码= 17.8秒(节点v4)

testing4 – 64位Macbook Pro,86,277素数(i = 8,000,00,001; i <1600,000,000,000; i + = 10000)

在C代码中使用无符号的长数据types,强制JavaScript使用全部64位。 – C代码= 35.8秒(铛,长数据types) – JS代码= 34.1秒(节点v4)

testing5 – 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% 

testing6 – 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数据types更改为整数可显着加快执行速度。 在BBB和Cloud9计算机上,切换为整数使得C比node.js快。 但在我的Mac上,node.js程序仍然跑得更快。 也许是因为Mac正在使用clang,而BBB和Cloud 9计算机正在使用gcc。 有谁知道gcc编译比gcc更快的程序吗?

当使用全部64位整数时,C比BBB和Cloud9 PC上的node.js快一点,但在MAC上慢一点。

你也可以看到在这些testing中,pypy比标准python快大约四倍。

node.js与C兼容的事实使我感到惊讶。

我花了几天的时间研究JS / V8和C之间的性能差异,首先关注V8发动机产生的氢气红外。 然而,在确定没有特别的优化之后,我又回到了对程序集输出的分析,结果让我觉得答案是非常简单的,在Jay Conrod的博客文章中 , V8:

根据规范,JavaScript中的所有数字都是64位浮点数。 我们经常使用整数,所以只要有可能V8代表带有31位有符号整数的数字

这个例子允许用32位来拟合所有的计算,而node.js充分利用了这一点! C代码使用longtypes,在OP(和我的)平台上恰好是64位types。 因此,这是一个32位算术比64位算术问题,主要是由于昂贵的除法/余数操作。

如果将C代码中的longreplace为int ,那么由gcc产生的二进制代码会节点node.js.

而且,如果循环在32位数范围之外的范围内寻找素数,则node.js版本的性能会显着下降。


certificate

使用的源代码可在结果下面的答案中find。

用C和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 

在签名的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); }