web-dev-qa-db-fra.com

Pourquoi JavaScript semble être 4 fois plus rapide que C++?

Pendant longtemps, j'avais pensé que le C++ était plus rapide que le JavaScript. Cependant, aujourd’hui, j’ai réalisé un script de référence pour comparer la vitesse des calculs en virgule flottante dans les deux langues et le résultat est incroyable!

JavaScript semble être presque 4 fois plus rapide que C++!

Je laisse les deux langues faire le même travail sur mon ordinateur portable i5-430M, en exécutant a = a + b pour 100000000 fois. C++ prend environ 410 ms, tandis que JavaScript ne prend qu'environ 120 ms.

Je n'ai vraiment aucune idée de la raison pour laquelle JavaScript est si rapide dans ce cas. Quelqu'un peut-il expliquer cela?

Le code que j'ai utilisé pour le JavaScript est (exécuté avec Node.js):

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

Et le code pour C++ (compilé par g ++) est:

#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

J'ai peut-être de mauvaises nouvelles pour vous si vous utilisez un système Linux (conforme à POSIX au moins dans cette situation). Le clock() call renvoie le nombre de ticks d'horloge consommés par le programme et mis à l'échelle par CLOCKS_PER_SEC, qui est 1,000,000.

Cela signifie que, si vous êtes sur un tel système, vous parlez en microsecondes pour C et millisecondes pour JavaScript (conformément au JS en ligne docs ). Ainsi, plutôt que JS étant quatre fois plus rapide, C++ est en réalité 250 fois plus rapide.

Maintenant, il se peut que vous soyez sur un système où CLOCKS_PER_SECOND est différent d'un million, vous pouvez exécuter le programme suivant sur votre système pour voir s'il est mis à l'échelle avec la même valeur:

#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;
}

La sortie sur ma boîte est:

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

montrant que le facteur d'échelle est un million. Si vous exécutez ce programme ou étudiez CLOCKS_PER_SEC et que c'est non un facteur d'échelle d'un million, vous devez examiner d'autres éléments.


La première étape consiste à vous assurer que votre code est réellement optimisé par le compilateur. Cela signifie, par exemple, que vous définissez -O2 ou -O3 pour gcc.

Sur mon système avec du code non optimisé, je vois:

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

et c'est trois fois plus rapide avec -O2, bien qu'avec une réponse légèrement différente, bien que d'environ un millionième de pour cent:

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

Cela ramènerait les deux situations sur un pied d'égalité, ce à quoi je m'attendrais, car JavaScript n'est pas une bête interprétée comme au bon vieux temps, chaque jeton étant interprété chaque fois qu'il est vu.

Les moteurs JavaScript modernes (V8, Rhino, etc.) peuvent compiler le code sous une forme intermédiaire (ou même en langage machine), ce qui permet des performances sensiblement égales à celles de langages compilés comme C.

Mais, pour être honnête, vous n'avez pas tendance à choisir JavaScript ou C++ pour sa rapidité, vous les choisissez pour leurs points forts. Il n'y a pas beaucoup de compilateurs C flottant dans les navigateurs et je n'ai pas remarqué beaucoup de systèmes d'exploitation ni d'applications intégrées écrites en JavaScript.

194
paxdiablo

En faisant un test rapide avec l'optimisation d'activation, j'ai obtenu des résultats d'environ 150 ms pour un ancien processeur AMD 64 X2 et d'environ 90 ms pour un processeur Intel i7 raisonnablement récent.

Ensuite, j'ai fait un peu plus pour donner une idée de l'une des raisons pour lesquelles vous pourriez vouloir utiliser le C++. J'ai déroulé quatre itérations de la boucle, pour obtenir ceci:

#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;
}

Cela laisse le code C++ s'exécuter dans environ 44 ms sur AMD (j'ai oublié d'exécuter cette version sur Intel). Ensuite, j'ai activé le vectoriseur automatique du compilateur (-Qpar avec VC++). Cela a réduit le temps un peu plus loin, à environ 40 ms sur l’AMD et à 30 ms sur l’Intel.

En bout de ligne: si vous voulez utiliser le C++, vous devez vraiment apprendre à utiliser le compilateur. Si vous voulez obtenir de très bons résultats, vous voudrez probablement aussi apprendre à écrire un meilleur code.

Je devrais ajouter: je n’ai pas essayé de tester une version sous Javascript avec la boucle déroulée. Cela pourrait également apporter une amélioration similaire (ou du moins une amélioration) de la vitesse dans JS. Personnellement, je pense que rendre le code rapide est beaucoup plus intéressant que de comparer Javascript à C++.

Si vous voulez que le code comme celui-ci soit rapide, déroulez la boucle (au moins en C++).

Depuis que le sujet de l'informatique parallèle est apparu, j'ai pensé ajouter une autre version sous OpenMP. Pendant que j'y étais, j'ai nettoyé un peu le code pour que je puisse garder une trace de ce qui se passait. J'ai également modifié un peu le code de synchronisation pour afficher l'heure globale au lieu de l'heure de chaque exécution de la boucle interne. Le code résultant ressemblait à ceci:

#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;
}

Le principal ajout ici est la ligne suivante (certes quelque peu arcanique):

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

Cela indique au compilateur d'exécuter la boucle externe dans plusieurs threads, avec une copie distincte de inc pour chaque thread et en additionnant les valeurs individuelles de total après la section parallèle.

Le résultat correspond à ce que vous attendez probablement. Si nous n'activons pas OpenMP avec l'indicateur -openmp du compilateur, le temps indiqué est environ 10 fois supérieur à celui observé auparavant pour les exécutions individuelles (409 ms pour le processeur AMD, 323 MS pour le processeur Intel). Avec OpenMP activé, le temps passe à 217 ms pour l’AMD et à 100 ms pour l’Intel.

Ainsi, sur le processeur Intel, la version originale prenait 90 ms pour une itération de la boucle externe. Avec cette version, les 10 itérations de la boucle externe s’allongent légèrement (100 ms), ce qui représente une amélioration de la vitesse d’environ 9: 1. Sur une machine avec plus de cœurs, nous pourrions nous attendre à encore plus d'amélioration (OpenMP tirera normalement profit de tous les cœurs disponibles automatiquement, bien que vous puissiez ajuster manuellement le nombre de threads si vous le souhaitez).

8
Jerry Coffin

C'est un sujet polarisant, on peut donc regarder:

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

Analyse comparative de toutes sortes de langues.

Javascript V8 et autres font sûrement du bon boulot pour les boucles simples comme dans l'exemple, générant probablement un code machine très similaire . le gaspillage et les nombreuses conséquences inévitables sur les performances (et le manque de contrôle) pour des algorithmes/applications plus compliqués. 

1
Raymund Hofmann