Wie kann node.js schneller sein als C und Java? Benchmark zum Vergleich von node.js, c, Java und Python

Lesezeit: 7 Minuten

Benutzer-Avatar
Timotheus Vann

Ich habe ein sehr einfaches Benchmarking-Programm erstellt, das alle Primzahlen bis 10.000.000 in 4 verschiedenen Sprachen berechnet.

  • (2,97 Sekunden) – node.js (Javascript) (4.4.5)
  • (6,96 Sekunden) – c (c99)
  • (6,91 Sekunden) – Java (1,7)
  • (45,5 Sekunden) – Python (2,7)

oben ist der Durchschnitt von jeweils 3 Läufen, Benutzerzeit

Node.js läuft mit Abstand am schnellsten. Das verwirrt mich aus zwei Gründen:

  1. Javascript verwendet immer Floats mit doppelter Genauigkeit für Variablen, während C und Java in diesem Fall (lange) Ganzzahlen verwenden. Mathematik mit ganzen Zahlen sollte schneller sein.
  2. Javascript wird oft als interpretiert bezeichnet, obwohl es sich tatsächlich um eine just in time kompilierte Sprache handelt. Aber wie kann der JIT-Compiler trotzdem schneller sein als eine vollständig kompilierte Sprache? Der Python-Code läuft am langsamsten, was keine Überraschung ist, aber warum läuft der node.js-Code nicht mit einer ähnlichen Geschwindigkeit wie der Python?

Ich habe den C-Code mit -O2-Optimierung kompiliert, aber ich habe es mit allen Optimierungsstufen versucht und es hat keinen merklichen Unterschied gemacht.

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-Ergebnisse

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

$ 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

öffentliche Klasse 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-Ergebnisse

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

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

zusätzliche c Laufzeiten (Nachtrag)

  • (7,81 s) keine Optimierung, gcc primeCalc.c -lm -std=c99 -Wall
  • (8,13 s) Optimierung 0, gcc primeCalc.c -lm -O0 -std=c99 -Wall
  • (7,30 s) Optimierung 1, gcc primeCalc.c -lm -O1 -std=c99 -Wall
  • (6,66 s) Optimierung 2, gcc primeCalc.c -lm -O2 -std=c99 -Wall

    • durchschnittlich 3 neue Läufe pro Optimierungsstufe Benutzerzeit *

Ich habe den Beitrag hier gelesen: Warum ist dieses NodeJS 2x schneller als natives C? Dieser Code verwendet ein Beispiel, das eigentlich nichts Bedeutendes bewirkt. Es ist, als könnte der Compiler das Ergebnis zur Kompilierzeit herausfinden und muss die Schleife nicht einmal 100000000 Mal ausführen, um die Antwort zu erhalten. Fügt man der Rechnung einen weiteren Teilungsschritt hinzu, fällt die Optimierung viel weniger ins Gewicht.

for (long i = 0; i < 100000000; i++) {
  d += i >> 1;    
  d = d / (i +1); // <-- New Term 
}
  • (1,88 Sekunden) ohne Optimierung
  • (1,53 Sekunden) mit Optimierung (-O2)

Update 15.03.2017
Nachdem ich die Antwort von @leon gelesen hatte, führte ich einige Verifizierungstests durch.

Test 1 – 32 Bit Beaglebone Black, 664.579 Primzahlen bis zu 10.000.000

Unbearbeitete calcPrime.js und calcPrime.c laufen auf dem Beaglebone Black, der einen 32-Bit-Prozessor hat.

  • C-Code = 62 Sekunden (gcc, langer Datentyp)
  • JS-Code = 102 Sekunden (Knoten v4)

Test 2 – 64 Bit Macbook Pro, 664.579 Primzahlen bis zu 10.000.000

Ersetzen Sie lange Datentypen im calcPrime.c-Code durch uint32_t und laufen Sie auf meinem MacBook Pro mit einem 64-Bit-Prozessor.

  • C-Code = 5,73 Sekunden (Klang, langer Datentyp)
  • C-Code = 2,43 Sekunden (clang, uint_32_t-Datentyp)
  • JS-Code = 2,12 Sekunden (Knoten v4)

Test 3 – 64 Bit Macbook Pro, 91.836 Primzahlen (i=1; i < 8.000.000.000; i+=10000)

Verwenden Sie unsignierte lange Datentypen in C-Code, erzwingen Sie Javascript, um 64 Bit zu verwenden. – C-Code = 20,4 Sekunden (Clang, langer Datentyp) – JS-Code = 17,8 Sekunden (Knoten v4)

Test 4 – 64 Bit Macbook Pro, 86.277 Primzahlen (i = 8.000.00.001; i < 16.000.000.000; i+=10000)

Verwenden Sie unsignierte lange Datentypen in C-Code, erzwingen Sie Javascript, alle 64 Bit zu verwenden. – C-Code = 35,8 Sekunden (Clang, langer Datentyp) – JS-Code = 34,1 Sekunden (Knoten 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%

(Alle Ergebnisse sind der Durchschnitt der Benutzersekunden für drei Läufe, Zeitabweichung zwischen Läufen < 10 %)

Gemischte Resultate

Das Ändern des C- und Java-Datentyps in Integer im Bereich von Integern beschleunigte die Ausführung erheblich. Auf den BBB- und Cloud9-Computern machte der Wechsel zu ints C schneller als node.js. Aber auf meinem Mac lief das Programm node.js immer noch schneller. Vielleicht, weil der Mac clang verwendet und die BBB- und Cloud 9-Computer gcc verwenden. Weiß jemand, ob gcc Programme schneller kompiliert als gcc?

Bei Verwendung aller 64-Bit-Integer war C etwas schneller als node.js auf dem BBB- und Cloud9-PC, aber etwas langsamer auf meinem MAC.

Sie können auch sehen, dass pypy in diesen Tests etwa viermal schneller ist als Standard-Python.

Dass node.js sogar zu C kompatibel ist, erstaunt mich.

  • v8 ist optimieren. In der Tat, v8 ist sehr optimieren. Jedoch, in acht nehmen Mikro-Benchmarks.

    – Elliot Frisch

    7. September 2016 um 2:39 Uhr

  • vielleicht legst du mit dem Finger auf etwas long zu double Konvertierung, diese könnten auf lange Sicht kostspielig sein

    – dvhh

    7. September 2016 um 2:41 Uhr

  • Mögliches Duplikat von Warum ist dieses NodeJS 2x schneller als natives C?

    – EnRaiser

    7. September 2016 um 2:44 Uhr

  • @enRaiser Hier, gcc wird bestanden -O2 schon.

    – Elliot Frisch

    7. September 2016 um 2:47 Uhr

  • -O2 -m64 senkte den Durchschnitt bei drei Läufen von 6,66 auf 6,41 Sekunden, aber die Ergebnisse variieren jedes Mal um ein paar Zehntelsekunden, sodass ich nicht sagen kann, dass es tatsächlich ein signifikantes Ergebnis hatte.

    – Timothy Vann

    7. September 2016 um 3:24 Uhr

Benutzer-Avatar
Leon

Ich verbrachte ein paar Tage damit, den Leistungsunterschied zwischen JS/V8 und C zu untersuchen, wobei ich mich zunächst auf die vom V8-Motor erzeugte Wasserstoff-IR konzentrierte. Nachdem ich mich jedoch vergewissert hatte, dass dort keine außergewöhnlichen Optimierungen vorhanden sind, kehrte ich zur Analyse der Assembly-Ausgabe zurück und es fiel mir auf, dass die Antwort sehr einfach war und sich auf die paar Sätze darin reduzierte Blogbeitrag von Jay Conrod auf Interna von V8:

Gemäß der Spezifikation sind alle Zahlen in JavaScript 64-Bit-Gleitkommadoppelungen. Wir arbeiten jedoch häufig mit ganzen Zahlen, also V8 stellt Zahlen mit 31-Bit-Ganzzahlen mit Vorzeichen dar, wann immer dies möglich ist.

Das vorliegende Beispiel ermöglicht es, alle Berechnungen in 32 Bit einzupassen, und node.js nutzt das voll aus! Der C-Code verwendet die long Typ, der auf der Plattform von OP (sowie meiner) zufällig ein 64-Bit-Typ ist. Daher ist es ein 32-Bit-Arithmetik- vs. 64-Bit-Arithmetikproblem, hauptsächlich aufgrund der teuren Division/Rest-Operation.

Wenn long im C-Code wird durch ersetzt intdann schlägt die von gcc erzeugte Binärdatei node.js.

Wenn die Schleife außerdem dazu gebracht wird, nach Primzahlen über einen Bereich zu suchen, der außerhalb des Bereichs von 32-Bit-Zahlen liegt, sinkt die Leistung der node.js-Version erheblich.


Nachweisen

Den verwendeten Quellcode finden Sie weiter unten in der Antwort unter den Ergebnissen.

Mit C und node.js werden weniger als 10 Millionen Primzahlen gezählt

$ 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 # Zähle Primzahlen <10M unter Verwendung von C-Code mit (64-Bit) langem Typ $ time ./count_primes_long 0 10000000 Der Bereich [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

Counting the primes in the range of length 100,000 starting at the number contained in the first column:

              | 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(prozess.argv[3]);  var E = S+N;  var P = countPrime(S, E);  console.log('Der Bereich [', 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  \n");  Ausgang (1);  } const long S = atol(argv[1]);  const long N = atol(argv[2]);  lange Zählung registrieren = 0;  for (register long i = S; i < S + N; i++){ if ( isPrime(i) ) ++count;  } printf("Der Bereich [%li, %li) contains %li primes\n", S, S+N, count);
}

  • What's the reason for JS/V8 being faster than C?

    – mavavilj

    Sep 4 at 12:08

user avatar
Randiaz

When I ran your code in python with a new algorithm:

real    0m3.583s
user    0m3.297s
sys     0m0.094s

Faster than the C benchmark you have above. I think that a simpler language helps you design better algorithms, but that is my opinion.
(Also can use multiprocessing to make even faster)

def allPrimes(N):
    is_prime = [1]*N # Wir wissen, dass 0 und 1 zusammengesetzte is_prime sind[0] = 0 is_prime[1] = 0 i = 2 # Dies wird eine Schleife von 2 zu int(sqrt(x)) machen, während i*i <= N: # Wenn wir diese Zahl bereits durchgestrichen haben, fahren Sie fort, wenn is_prime[i] == 0: i += 1 weiter j = 2*i solange j < N: # Durchstreichen, da es zusammengesetzt ist is_prime[j] = 0 # j wird um i erhöht, weil wir alle Vielfachen von i abdecken wollen j += i i += 1 return is_prime print("total", sum(allPrimes(10000000)))

  • Was ist der Benchmark Ihres Algorithmus in C? Ich denke, in dieser Frage vergleichen wir die Sprachgeschwindigkeit, nicht die Algorithmusgeschwindigkeit. Natürlich könnte man einen besseren Algorithmus schreiben, aber Sie müssen den gleichen Algorithmus in verschiedenen Sprachen verwenden. Sie können Ihre Zeit nicht verwenden und sie mit der Algorithmuszeit von op vergleichen.

    – der Programmierer

    23. Juni 2021 um 3:55 Uhr

1371130cookie-checkWie kann node.js schneller sein als C und Java? Benchmark zum Vergleich von node.js, c, Java und Python

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy