Come può nodo.js essere più veloce di c e java? Nodo di confronto benchmark.js, c, java e python


Ho fatto un programma di benchmarking molto semplice che calcola tutti i numeri primi fino a 10.000.000 in 4 lingue diverse.

  • (2,97 secondi) - nodo.js (javascript) (4.4.5)
  • (6,96 secondi) - c (c99)
  • (6,91 secondi) - java (1,7)
  • (45,5 secondi) - python (2,7)

sopra è la media di 3 esecuzioni ciascuna, tempo utente

Nodo.js corre di gran lunga il più veloce. Questo mi confonde per due motivi:

  1. javascript utilizza sempre float a doppia precisione per le variabili mentre c e java utilizzano interi (lunghi) in questo caso. La matematica con numeri interi dovrebbe essere più veloce.
  2. javascript viene spesso definito come interpretato quando in realtà è un linguaggio compilato just in time. Ma anche così come può il compilatore JIT essere più veloce di un linguaggio completamente compilato? Il codice python esegue il più lento che non è una sorpresa, ma perché non è il nodo.codice js in esecuzione ad una velocità simile al pitone?

Ho compilato il codice c con l'ottimizzazione-O2, ma l'ho provato con tutti i livelli di ottimizzazione e non ha fatto una differenza notevole.

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

Nodo.risultati js

$ time node primeCalc.js
total 664579

real    0m2.965s
user    0m2.928s
sys     0m0.016s

$ node --version
v4.4.5

Primecalcolo.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 risultati

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

Primecalcolo.java

Classe pubblica 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;
  };

}

Risultati 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 risultati

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

Tempi di esecuzione c aggiuntivi (addendum)

  • (7.81 s) nessuna ottimizzazione, gcc primeCalc.c-lm-std=c99-Parete
  • (8.13 s) ottimizzazione 0, gcc primeCalc.c-lm-O0-std=c99-Parete
  • (7.30 s) ottimizzazione 1, gcc primeCalc.c-lm-O1-std=c99-Parete
  • (6.66 s) ottimizzazione 2, gcc primeCalc.c-lm-O2-std=c99-Parete

    • media di 3 nuove esecuzioni ogni livello di ottimizzazione tempo utente *

Ho letto il post qui: Perché questo NodeJS 2x è più veloce del C nativo? Questo codice utilizza un esempio che in realtà non fa nulla di significativo. È come se il compilatore potesse capire il risultato in fase di compilazione e non avesse nemmeno bisogno di eseguire il ciclo 100000000 volte per trovare la risposta. Se si aggiunge un altro passaggio di divisione al calcolo, l'ottimizzazione è molto meno significativa.

for (long i = 0; i < 100000000; i++) {
  d += i >> 1;    
  d = d / (i +1); // <-- New Term 
}
  • (1,88 secondi) senza ottimizzazione
  • (1,53 secondi) con ottimizzazione (- O2)

Aggiornamento 03/15/2017 Dopo aver letto la risposta di @ leon ho eseguito alcuni test di verifica.

Prova 1-32 Bit Beaglebone nero, 664,579 primi fino a 10,000,000

Inedito calcPrime.js e calcPrime.c in esecuzione sul Beaglebone nero che ha un processore a 32 bit.

  • Codice C = 62 secondi (gcc, tipo di dati lunghi)
  • Codice JS = 102 secondi (nodo v4)

Prova 2-Macbook Pro a 64 bit, 664.579 numeri primi fino a 10.000.000

Sostituire i tipi di dati lunghi in calcPrime.codice c con uint32_t e in esecuzione sul mio MacBook pro che ha un processore a 64 bit.

  • Codice C = 5,73 secondi (clang, tipo di dati lunghi)
  • Codice C = 2,43 secondi (clang, tipo di dati uint_32_t)
  • Codice JS = 2,12 secondi (nodo v4)

Prova 3-64 Bit Macbook Pro, 91,836 numeri primi (i=1; i

Usa tipi di dati lunghi non firmati nel codice C, forza javascript a usare alcuni 64 bit. - C codice = 20.4 secondi (clang, lungo tipo di dati) - Codice JS = 17,8 secondi (nodo v4)

Prova 4-64 Bit Macbook Pro, 86,277 numeri primi (i = 8,000,00,001; i

Usa i tipi di dati lunghi non firmati nel codice C, forza javascript a usare tutti i 64 bit. - C codice = 35.8 secondi (clang, lungo tipo di dati) - Codice JS = 34,1 secondi (nodo v4)

Prova 5-Cloud9 64-Bit Linux, (i = 0; 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%

Prova 6-Cloud9 64-Bit Linux, (i = 8000000001; i

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%

( Tutti i risultati sono la media dei secondi utente per tre esecuzioni, variazione di tempo tra le esecuzioni

Risultati misti

La modifica del tipo di dati C e Java in integer quando nell'intervallo di numeri interi ha accelerato significativamente l'esecuzione. Sul BBB e Cloud9 i computer che passano a int hanno reso C più veloce del nodo.js. Ma sul mio Mac il nodo.il programma js funzionava ancora più velocemente. Forse perché il Mac sta usando clang e i computer BBB e Cloud 9 stanno usando gcc. Qualcuno sa se gcc compila programmi più veloci di gcc?

Quando si utilizzavano tutti gli interi a 64 bit C era un po ' più veloce del nodo.js sul PC BBB e Cloud9 ma un po ' più lento sul mio MAC.

Puoi anche vedere che pypy è circa quattro volte più veloce di python standard in questi test.

Il fatto che il nodo.js è anche compatibile con C mi stupisce.

Author: Community, 2016-09-07

1 answers

Ho passato un paio di giorni a studiare la differenza di prestazioni tra JS/V8 e C, concentrandomi prima di tutto sull'idrogeno IR generato dal motore V8. Tuttavia, dopo essermi assicurato che non fossero presenti ottimizzazioni straordinarie, sono tornato all'analisi dell'output dell'assembly e mi ha colpito che la risposta fosse molto semplice, riducendomi al paio di frasi in Il post sul blog di Jay Conrod su interni di V8:

Secondo le specifiche, tutti i numeri in JavaScript sono doppi in virgola mobile a 64 bit. Spesso lavoriamo con numeri interi, quindi V8 rappresenta i numeri con numeri interi firmati a 31 bit quando possibile.

L'esempio a portata di mano consente di adattare tutti i calcoli in 32 bit e nodo.js ne approfitta appieno! Il codice C utilizza il tipo long, che sulla piattaforma OP (così come la mia) sembra essere un tipo a 64 bit. Pertanto, è un problema aritmetico a 32 bit rispetto a 64 bit, principalmente dovuto a la costosa operazione divisione / resto.

Se long nel codice C viene sostituito con int, allora il binario prodotto da gcc batte il nodo.js.

Inoltre, se il ciclo è fatto per cercare i numeri primi su un intervallo che è al di fuori del regno dei numeri a 32 bit, le prestazioni del nodo.la versione js scende in modo significativo.


Prova

Il codice sorgente utilizzato si trova più avanti nella risposta, sotto i risultati.

Conteggio dei numeri primi inferiore a 10 milioni con C e nodo.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

Figure di performance in prossimità del limite di interi a 32 bit con segno

Contando i numeri primi nell'intervallo di lunghezza 100.000 a partire dal numero contenuto nella prima colonna:

              | 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);
}
 15
Author: Leon, 2017-03-15 15:57:26