node.js怎么能比c和java更快?比较节点.js、c、java 和 python 的基准测试
我做了一个非常简单的基准测试程序,可以计算4种不同语言的所有素数,最高可达10,000,000。
- (2.97 秒) - 节点.js (javascript) (4.4.5)
- (6.96 秒) - c (c99)
- (6.91 秒) - 爪哇 (1.7)
- (45.5 秒) - 蟒蛇 (2.7)
以上是平均每次3次运行,用户时间
Node.js到目前为止运行得最快。这让我感到困惑,原因有两个:
- javascript总是对变量使用双精度浮点数,而在这种情况下,c和java使用(长)整数。带整数的数学运算应该更快。
- javascript通常被称为解释,而实际上它是一种及时编译的语言。但即便如此,JIT编译器如何比完全编译的语言更快呢?python代码运行速度最慢,这并不奇怪,但是为什么节点.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();
节点.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;
};
}
爪哇结果
$ 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
额外的 c 运行时间(附录)
- (7.81 s) 无优化, gcc primeCalc.c -lm -std=c99 -Wall
- (8.13 s) optimization 0, gcc primeCalc.c -lm -O0 -std=c99 -Wall
- (7.30 s) optimization 1, gcc primeCalc.c -lm -O1 -std=c99 -Wall
-
(6.66 s) optimization 2, gcc primeCalc.c -lm -O2 -std=c99 -Wall
- 平均 3 次新运行,每个优化级别用户时间 *
我在这里读了这篇文章:为什么这个NodeJS比原生C快2倍?此代码使用的示例实际上并不执行任何重要操作。就好像编译器可以在编译时找出结果,甚至不需要执行循环100000000次就可以得出答案。如果在计算中添加另一个除法步骤,则优化的重要性要小得多。
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的答案后,我进行了一些验证测试。
测试 1 - 32 位 Beaglebone Black,664,579 个素数,最高 10,000,000 个
未经编辑的calcPrime.js和calcPrime.c运行在Beaglebone black上,该黑色具有32位处理器。
- C 代码 = 62 秒(gcc,长数据类型)
- JS 代码 = 102 秒(节点 v4)
测试 2 - 64 位 Macbook Pro,664,579 个素数,最高 10,000,000 个
将calcPrime.c代码中的长数据类型替换为uint32_t并在具有64位处理器的MacBook Pro上运行。
- C 代码 = 5.73 秒(叮当声,长数据类型)
- C 代码 = 2.43 秒(clang、uint_32_t 数据类型)
- JS 代码 = 2.12 秒(节点 v4)
测试 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 秒(clang,长数据类型)- JS 代码 = 34.1 秒(节点 v4)
Test 5 - Cloud9 64-Bit 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%
Test 6 - Cloud9 64-Bit 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计算机上,切换到ints使C比node.js更快。但是在我的Mac上,节点.js程序仍然运行得更快。也许是因为Mac正在使用clang,而BBB和Cloud 9计算机正在使用gcc。有谁知道gcc是否编译比gcc更快的程序?
当使用所有64位整数时,C比node快一点.js在BBB和Cloud9 PC上,但在我的MAC上慢一点。
您还可以看到,在这些测试中,pypy 比标准 python 快四倍。
node.js甚至与C兼容的事实让我感到惊讶。