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

标签 java python c node.js

我制作了一个非常简单的基准测试程序,可以计算 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 运行速度最快。这让我感到困惑,原因有两个:
  • javascript 总是对变量使用 double 浮点数,而 c 和 java 在这种情况下使用(长)整数。整数数学应该更快。
  • 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
    

    素数计算器
    #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

    公共(public)类 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
    

    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 秒)优化 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 次新运行 *

  • 我在这里阅读了帖子:
    Why is this NodeJS 2x faster than native C?
    此代码使用的示例实际上并没有做任何重要的事情。就好像编译器可以在编译时计算出结果,甚至不需要执行循环 100000000 次就可以得出答案。
    如果在计算中添加另一个除法步骤,则优化的重要性要小得多。
    for (long i = 0; i < 100000000; i++) {
      d += i >> 1;    
      d = d / (i +1); // <-- New Term 
    }
    
  • (1.88 秒)未优化
  • (1.53 秒)优化 (-O2)


  • 2017 年 3 月 15 日更新
    在阅读@leon 的答案后,我进行了一些验证测试。

    测试 1 - 32 位 Beaglebone Black,664,579 个质数,最多 10,000,000

    未经编辑的 calcPrime.js 和 calcPrime.c 在具有 32 位处理器的 Beaglebone black 上运行。
  • C 代码 = 62 秒(gcc,长数据类型)
  • JS 代码 = 102 秒( Node v4)

  • 测试 2 - 64 位 Macbook Pro,664,579 个质数高达 10,000,000

    用 uint32_t 替换 calcPrime.c 代码中的长数据类型,并在我的 MacBook pro 上运行,它有 64 位处理器。
  • C 代码 = 5.73 秒(clang,长数据类型)
  • C 代码 = 2.43 秒(clang,uint_32_t 数据类型)
  • JS 代码 = 2.12 秒( Node v4)

  • 测试 3 - 64 位 Macbook Pro,91,836 个素数(i=1;i < 8,000,000,000;i+=10000)

    在 C 代码中使用 unsigned long 数据类型,强制 javascript 使用一些 64 位。
    - C 代码 = 20.4 秒(clang,长数据类型)
    - JS 代码 = 17.8 秒( Node v4)

    测试 4 - 64 位 Macbook Pro,86,277 个质数(i = 8,000,00,001;i < 16,000,000,000;i+=10000)

    在 C 代码中使用 unsigned long 数据类型,强制 javascript 使用所有 64 位。
    - C 代码 = 35.8 秒(clang,长数据类型)
    - JS 代码 = 34.1 秒( Node 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%
    

    (所有结果都是用户 3 次运行的平均秒数,运行之间的时间变化 < 10%)

    喜忧参半

    在整数范围内将 C 和 Java 数据类型更改为整数可显着加快执行速度。在 BBB 和 Cloud9 计算机上,切换到整数使 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's blog post 中的几句话。关于 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.



    手头的示例允许以 32 位拟合所有计算,并且 node.js 充分利用了这一点! C 代码使用 long类型,在 OP(以及我的)平台上恰好是 64 位类型。因此,这是一个 32 位算术与 64 位算术问题,主要是由于昂贵的除法/余数运算。

    long在 C 代码中替换为 int ,然后由 gcc 生成的二进制文件击败了 node.js。

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

    证明

    使用的源代码在结果下方的答案中进一步找到。

    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



    从第一列中包含的数字开始计算长度为 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);
    }
    

    关于java - node.js 如何比 c 和 java 更快?基准比较 node.js、c、java 和 python,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39360403/

    相关文章:

    c - qsort 结构数组降序

    JavaFX Listview - 禁用双击编辑?

    java - 如何指挥机器人

    java - Gradle:子模块中抛出的类错误没有这样的属性?

    java - API 服务每隔一个请求工作一次

    python - 使用不同用户表的 Django 中的登录和注册示例

    python - 在for语句中的django模板中按索引访问列表?

    c - 来自用户输入的字符串被转换为字母模式。

    c - C程序中变量的地址空间变化

    python - 使用OpenCV检测和跟踪人的手