web-dev-qa-db-de.com

Warum scheint JavaScript viermal schneller zu sein als C++?

Ich hatte lange gedacht, dass C++ schneller ist als JavaScript. Heute habe ich jedoch ein Benchmark-Skript erstellt, um die Geschwindigkeit der Fließkommaberechnungen in den beiden Sprachen zu vergleichen.

JavaScript scheint fast viermal schneller als C++ zu sein!

Ich lasse beide Sprachen auf meinem i5-430M-Laptop die gleiche Arbeit machen und a = a + b für 100000000-mal ausführen. C++ dauert etwa 410 ms, während JavaScript nur etwa 120 ms benötigt.

Ich habe wirklich keine Ahnung, warum JavaScript in diesem Fall so schnell läuft. Kann das jemand erklären?

Der Code, den ich für das JavaScript verwendet habe, lautet (mit Node.js ausgeführt):

(function() {
    var a = 3.1415926, b = 2.718;
    var i, j, d1, d2;
    for(j=0; j<10; j++) {
        d1 = new Date();
        for(i=0; i<100000000; i++) {
            a = a + b;
        }
        d2 = new Date();
        console.log("Time Cost:" + (d2.getTime() - d1.getTime()) + "ms");
    }
    console.log("a = " + a);
})();

Und der Code für C++ (von g ++ kompiliert) lautet:

#include <stdio.h>
#include <ctime>

int main() {
    double a = 3.1415926, b = 2.718;
    int i, j;
    clock_t start, end;
    for(j=0; j<10; j++) {
        start = clock();
        for(i=0; i<100000000; i++) {
            a = a + b;
        }
        end = clock();
        printf("Time Cost: %dms\n", (end - start) * 1000 / CLOCKS_PER_SEC);
    }
    printf("a = %lf\n", a);
    return 0;
}
25
streaver91

Ich kann einige schlechte Nachrichten für Sie haben, wenn Sie auf einem Linux -System arbeiten (das zumindest in dieser Situation mit POSIX kompatibel ist). Der Aufruf clock() gibt die Anzahl der vom Programm verbrauchten und mit CLOCKS_PER_SEC skalierten Zeitmarken zurück, die 1,000,000 sind.

Das heißt, wenn Sie auf einem solchen System arbeiten, sprechen Sie in Mikrosekunden für C und Millisekunden für JavaScript (gemäß JS-Online-Dokumentation ). Anstatt dass JS viermal schneller ist, ist C++ 250-mal schneller.

Nun kann es sein, dass Sie sich auf einem System befinden, auf dem CLOCKS_PER_SECOND nicht eine Million ist. Sie können das folgende Programm auf Ihrem System ausführen, um festzustellen, ob es auf denselben Wert skaliert ist:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define MILLION * 1000000

static void commaOut (int n, char c) {
    if (n < 1000) {
        printf ("%d%c", n, c);
        return;
    }

    commaOut (n / 1000, ',');
    printf ("%03d%c", n % 1000, c);
}

int main (int argc, char *argv[]) {
    int i;

    system("date");
    clock_t start = clock();
    clock_t end = start;

    while (end - start < 30 MILLION) {
        for (i = 10 MILLION; i > 0; i--) {};
        end = clock();
    }

    system("date");
    commaOut (end - start, '\n');

    return 0;
}

Die Ausgabe auf meiner Box ist:

Tuesday 17 November  11:53:01 AWST 2015
Tuesday 17 November  11:53:31 AWST 2015
30,001,946

dies zeigt, dass der Skalierungsfaktor eine Million ist. Wenn Sie dieses Programm ausführen oder CLOCKS_PER_SEC untersuchen und es ist kein Skalierungsfaktor von einer Million, müssen Sie sich einige andere Dinge ansehen.


Der erste Schritt besteht darin, sicherzustellen, dass Ihr Code tatsächlich vom Compiler optimiert wird. Dies bedeutet zum Beispiel, dass -O2 oder -O3 für gcc eingestellt wird.

Auf meinem System mit nicht optimiertem Code sehe ich:

Time Cost: 320ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
Time Cost: 300ms
a = 2717999973.760710

und es ist dreimal schneller mit -O2, wenn auch mit einer etwas anderen Antwort, wenn auch nur um ein Millionstel Prozent:

Time Cost: 140ms
Time Cost: 110ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
Time Cost: 100ms
a = 2718000003.159864

Das würde die beiden Situationen wieder auf die gleiche Stufe bringen, was ich erwarten würde, da JavaScript kein interpretiertes Biest wie früher ist, bei dem jedes Token immer dann interpretiert wird, wenn es gesehen wird.

Moderne JavaScript-Engines (V8, Rhino usw.) können den Code in eine Zwischenform (oder sogar in eine Maschinensprache) kompilieren, die eine Leistung ermöglicht, die in etwa der von kompilierten Sprachen wie C entspricht.

Um ehrlich zu sein, wählen Sie JavaScript oder C++ nicht wegen seiner Geschwindigkeit, sondern wegen seiner Stärken. Es gibt nicht viele C-Compiler in Browsern und ich habe nicht viele Betriebssysteme oder eingebettete Apps in JavaScript bemerkt.

202
paxdiablo

Bei einem schnellen Test mit dem Aktivieren der Optimierung erhielt ich für einen alten AMD 64 X2-Prozessor Ergebnisse von etwa 150 ms und für einen relativ neuen Intel i7-Prozessor etwa 90 ms.

Dann habe ich noch ein bisschen mehr gemacht, um einen Eindruck von einem Grund zu geben, aus dem Sie vielleicht C++ verwenden möchten. Ich habe vier Iterationen der Schleife abgerollt, um folgendes zu erhalten:

#include <stdio.h>
#include <ctime>

int main() {
    double a = 3.1415926, b = 2.718;
    double c = 0.0, d=0.0, e=0.0;
    int i, j;
    clock_t start, end;
    for(j=0; j<10; j++) {
        start = clock();
        for(i=0; i<100000000; i+=4) {
            a += b;
            c += b;
            d += b;
            e += b;
        }
        a += c + d + e;
        end = clock();
        printf("Time Cost: %fms\n", (1000.0 * (end - start))/CLOCKS_PER_SEC);
    }
    printf("a = %lf\n", a);
    return 0;
}

Dadurch konnte der C++ - Code in etwa 44 ms auf der AMD ausgeführt werden (vergaß die Ausführung dieser Version auf dem Intel). Dann habe ich den Auto-Vectorizer des Compilers (-Qpar mit VC++) eingeschaltet. Dies reduzierte die Zeit noch etwas, auf etwa 40 ms bei der AMD und 30 ms bei der Intel.

Fazit: Wenn Sie C++ verwenden möchten, müssen Sie wirklich lernen, wie der Compiler verwendet wird. Wenn Sie wirklich gute Ergebnisse erzielen möchten, möchten Sie wahrscheinlich auch lernen, besseren Code zu schreiben.

Ich sollte hinzufügen: Ich habe nicht versucht, eine Version unter Javascript mit der abgerollten Schleife zu testen. Dies könnte auch eine ähnliche (oder zumindest einige) Geschwindigkeitsverbesserung in JS bewirken. Ich persönlich denke, dass es viel interessanter ist, den Code schnell zu erstellen, als Javascript mit C++ zu vergleichen.

Wenn Sie möchten, dass Code wie dieser schnell ausgeführt wird, müssen Sie die Schleife (zumindest in C++) abwickeln.

Da das Thema Parallel Computing aufkam, dachte ich, ich würde mit OpenMP eine weitere Version hinzufügen. Während ich dabei war, habe ich den Code ein wenig aufgeräumt, damit ich den Überblick behalten konnte. Ich habe auch den Timing-Code ein wenig geändert, um die Gesamtzeit anstelle der Zeit für jede Ausführung der inneren Schleife anzuzeigen. Der resultierende Code sah folgendermaßen aus:

#include <stdio.h>
#include <ctime>

int main() {
    double total = 0.0;
    double inc = 2.718;
    int i, j;
    clock_t start, end;
    start = clock();

    #pragma omp parallel for reduction(+:total) firstprivate(inc)
    for(j=0; j<10; j++) {
        double a=0.0, b=0.0, c=0.0, d=0.0;
        for(i=0; i<100000000; i+=4) {
            a += inc;
            b += inc;
            c += inc;
            d += inc;
        }
        total += a + b + c + d;
    }
    end = clock();
    printf("Time Cost: %fms\n", (1000.0 * (end - start))/CLOCKS_PER_SEC);

    printf("a = %lf\n", total);
    return 0;
}

Die primäre Addition hier ist die folgende (zugegebenermaßen etwas arkane) Linie:

#pragma omp parallel for reduction(+:total) firstprivate(inc)

Dies weist den Compiler an, die äußere Schleife in mehreren Threads auszuführen, mit einer separaten Kopie von inc für jeden Thread, und addiert die einzelnen Werte von total nach dem Parallelabschnitt.

Das Ergebnis handelt davon, was Sie wahrscheinlich erwarten würden. Wenn wir OpenMP nicht mit dem -openmp-Flag des Compilers aktivieren, beträgt die angegebene Zeit etwa das Zehnfache der zuvor für die einzelnen Ausführungen (409 ms für AMD, 323 MS für Intel). Bei aktiviertem OpenMP fallen die Zeiten für AMD auf 217 ms und für Intel auf 100 ms.

Auf dem Intel benötigte die ursprüngliche Version 90 ms für eine Iteration der äußeren Schleife. Mit dieser Version werden wir für alle 10 Iterationen der äußeren Schleife etwas länger (100 ms) - eine Verbesserung der Geschwindigkeit um etwa 9: 1. Auf einer Maschine mit mehr Kernen können wir sogar noch mehr Verbesserungen erwarten (OpenMP nutzt normalerweise alle verfügbaren Kerne automatisch, obwohl Sie die Anzahl der Threads manuell anpassen können, wenn Sie möchten).

8
Jerry Coffin

Dies ist ein polarisierendes Thema, daher kann man sich folgendes ansehen:

https://benchmarksgame-team.pages.debian.net/benchmarksgame/

Benchmarking aller Arten von Sprachen.

Javascript V8 und dergleichen eignen sich sicherlich für einfache Schleifen wie im Beispiel, wobei wahrscheinlich sehr ähnlicher Maschinencode generiert wird .. Für die meisten Anwendungen, die sich "nah an den Benutzern" befinden, ist Javscript sicherlich die bessere Wahl Verschwendung und die oft unvermeidbare Leistung (und mangelnde Kontrolle) für kompliziertere Algorithmen/Anwendungen. 

1
Raymund Hofmann