Porque é que o JavaScript parece ser 4 vezes mais rápido que o C++?

Durante muito tempo, pensei que o c++ fosse mais rápido que o JavaScript. No entanto, hoje eu fiz um script de referência para comparar a velocidade dos cálculos de ponto flutuante nas duas línguas e o resultado é incrível!

o JavaScript parece ser quase 4 vezes mais rápido que o C++!

Deixei que ambas as línguas fizessem o mesmo trabalho no meu laptop i5-430M, realizando 100 milhões de vezes. O C++ leva cerca de 410 ms, enquanto o JavaScript leva apenas cerca de 120 ms.

A sério. não faço ideia porque o JavaScript corre tão rápido neste caso. Alguém pode explicar isso?

o código que usei para o JavaScript é (execute com nó.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);
})();

e o código para C++ (compilado por g++) é:

#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;
}
Author: Justin Krejcha, 2013-06-11

3 answers

Posso ter más notícias para si se estiver num sistema Linux (que está em conformidade com o POSIX pelo menos nesta situação). O clock() a chamada devolve o número de carraças de relógio consumidas pelo programa e escaladas por CLOCKS_PER_SEC, que é 1,000,000.

Que significa, se você estiver em tal sistema, você está falando em microssegundos para C e milissegundos para JavaScript (como por a JS documentos on-line). Então, em vez de JS ser quatro vezes mais rápido, C++ é 250 vezes mais rápido.

Agora pode ser que você esteja em um sistema onde CLOCKS_PER_SECOND é algo diferente de um milhão, você pode executar o seguinte programa em seu sistema para ver se ele é dimensionado pelo mesmo valor:
#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;
}

A saída na minha caixa é:

Tuesday 17 November  11:53:01 AWST 2015
Tuesday 17 November  11:53:31 AWST 2015
30,001,946
A mostrar que o factor de escala é um milhão. Se você executar esse programa, ou investigar CLOCKS_PER_SEC e não um fator de escala de um milhão, você precisa olhar para algum outro situacao.

O primeiro passo é garantir que o seu código está a ser optimizado pelo compilador. Isso significa, por exemplo, colocar -O2 ou -O3 para gcc.

No meu sistema com um código não optimizado, vejo:

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
E é três vezes mais rápido com -O2, embora com uma resposta ligeiramente diferente, embora apenas por cerca de um milionésimo de por cento:
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
Isso traria as duas situações de volta ao mesmo nível uma da outra, algo que eu esperaria desde então. JavaScript não é uma besta interpretada como nos velhos tempos, onde cada símbolo é interpretado sempre que é visto.

Motores JavaScript modernos (V8, Rhino, etc) podem compilar o código para uma forma intermediária (ou mesmo para linguagem de máquina) que pode permitir um desempenho aproximadamente igual a linguagens compiladas como C.

Mas, para ser honesto, você não tende a escolher JavaScript ou C++ para sua velocidade, você os escolhe para suas Áreas de força. Não há muitos compiladores C flutuando em torno de navegadores internos e eu não notei muitos sistemas operacionais nem aplicativos incorporados escritos em JavaScript.

 170
Author: paxdiablo, 2015-11-17 03:55:02
Fazendo um teste rápido com a ativação da otimização, obtive resultados de cerca de 150 ms para um antigo processador AMD 64 X2, e cerca de 90 ms para um processador Intel i7 razoavelmente recente.

Então eu fiz um pouco mais para dar uma ideia de uma razão para você querer usar C++. Eu desenrolei quatro iterações do loop, para conseguir isto:

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

Isto permite que o código C++ corra em cerca de 44ms no AMD (esqueceu-se de executar esta versão no Intel). Depois liguei o auto-vectorizador do compilador. (- Qpar com VC++). Isso reduziu o tempo um pouco mais ainda, para cerca de 40 ms no AMD, e 30 ms no Intel.

Conclusão: se quiser usar o C++, tem de aprender a usar o compilador. Se você quer obter realmente bons resultados, você provavelmente também quer aprender a escrever um código melhor.

Devo acrescentar que não tentei testar uma versão em Javascript com o loop desenrolado. Fazê-lo pode proporcionar uma melhoria similar (ou pelo menos alguma) da velocidade em JS também. Pessoalmente, acho que fazer o código rápido é muito mais interessante do que comparar Javascript com C++.

Se quiser que um código como este corra rápido, desenrole o loop (pelo menos em C++).

Uma vez que o assunto da computação paralela surgiu, pensei em Adicionar outra versão usando OpenMP. Enquanto estava lá, limpei um pouco o código, para poder acompanhar o que se estava a passar. Eu também alterei o código de tempo um pouco, para exibir o tempo total em vez do tempo para cada execução do laço interior. O código resultante era assim:
#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;
}

A adição primária aqui é a seguinte linha (reconhecidamente algo Arcana):

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

Isto diz ao compilador para executar o laço exterior em vários tópicos, com uma cópia separada de inc para cada tópico, e adicionando os valores individuais de total Após a secção paralela.

O resultado é o que se espera. Se não ativarmos o OpenMP com a bandeira do compilador -openmp, O Relatório o tempo é cerca de 10 vezes o que vimos anteriormente para execuções individuais (409 ms para a AMD, 323 MS para a Intel). Com o OpenMP ligado, o tempo passa para 217 ms para o AMD, e 100 ms para as informações. Então, segundo as informações, a versão original levou 90m por uma iteração do laço exterior. Com esta versão estamos ficando apenas um pouco mais longo (100 ms) para todas as 10 iterações do laço exterior -- uma melhoria na velocidade de cerca de 9:1. Numa máquina com mais núcleos, poderíamos esperar ainda mais melhoria (o OpenMP normalmente tirará vantagem de todos os núcleos disponíveis automaticamente, embora você possa afinar manualmente o número de threads se quiser).
 8
Author: Jerry Coffin, 2013-06-11 06:17:38
Este é um tópico polarizante, por isso pode - se ver:

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

([1]) aferir todos os tipos de línguas.

Javascript V8 e tal estão certamente fazendo um bom trabalho para loops simples como no exemplo, provavelmente gerando código de máquina muito semelhante. Para a maioria das aplicações" perto do Usuário " Javscript certamente é a melhor escolha, mas tenha em mente o desperdício de memória e as muitas vezes inevitáveis performance hit (and lack of control) for more complicated algorithms/applications.

 1
Author: Raymund Hofmann, 2018-09-11 15:01:37