为什么JavaScript看起来比C ++快4倍?

问题描述 投票:28回答:3

很长一段时间以来,我一直认为C ++比JavaScript更快。然而,今天我制作了一个基准脚本来比较两种语言中浮点计算的速度,结果令人惊叹!

JavaScript似乎比C ++快4倍!

我让这两种语言在我的i5-430M笔记本电脑上做同样的工作,执行a = a + b达1亿次。 C ++大约需要410毫秒,而JavaScript大约需要120毫秒。

我真的不知道为什么JavaScript在这种情况下运行如此之快。有谁能解释一下?

我用于JavaScript的代码是(使用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);
})();

C ++的代码(由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;
}
javascript c++ performance floating-point benchmarking
3个回答
196
投票

如果您使用的是Linux系统(至少在这种情况下符合POSIX),我可能会有一些坏消息。 clock()调用返回程序消耗的时钟滴答数,并由CLOCKS_PER_SEC缩放,即1,000,000

这意味着,如果您使用的是这样一个系统,那么您需要以微秒为单位谈论C和毫秒级的JavaScript(根据JS online docs)。因此,而不是JS快四倍,而C ++实际上要快250倍。

现在可能是你所在的系统中CLOCKS_PER_SECOND不是一百万,你可以在你的系统上运行以下程序,看它是否按相同的值缩放:

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

我的盒子上的输出是:

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

显示缩放因子是一百万。如果您运行该程序,或调查CLOCKS_PER_SEC并且它不是一百万的缩放因子,您需要查看其他一些事情。


第一步是确保编译器实际优化代码。这意味着,例如,为-O2设置-O3gcc

在我的系统中,未经优化的代码,我看到:

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

它使用-O2的速度提高了三倍,尽管回答略有不同,但只有百万分之一的百分之几:

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

这将使两种情况相互恢复,这是我所期待的,因为JavaScript不像过去那样被解释为野兽,每当它被看到时,每个标记都被解释。

现代JavaScript引擎(V8,Rhino等)可以将代码编译为中间形式(甚至是机器语言),这可能允许性能与C等编译语言大致相同。

但是,说实话,你不倾向于选择JavaScript或C ++来提高速度,你可以根据它们的强度来选择它们。浏览器内部没有很多C编译器,我没有注意到许多操作系统或用JavaScript编写的嵌入式应用程序。


8
投票

通过开启优化进行快速测试,我得到了一个古老的AMD 64 X2处理器大约150毫秒的结果,以及一个合理的最新英特尔i7处理器大约90毫秒的结果。

然后我做了一些更多的事情来了解你可能想要使用C ++的一个原因。我展开了循环的四次迭代,得到这个:

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

这让C ++代码在AMD上运行大约44ms(忘了在Intel上运行这个版本)。然后我打开了编译器的自动矢量化器(-Vpar with VC ++)。这样可以将时间进一步缩短,在AMD上大约40毫秒,在英特尔上大约30毫秒。

结论:如果你想使用C ++,你真的需要学习如何使用编译器。如果你想获得非常好的结果,你可能还想学习如何编写更好的代码。

我应该补充一点:我没有尝试在循环展开的Javascript下测试版本。这样做可能会在JS中提供类似(或至少某些)的速度提升。就个人而言,我认为快速编写代码比将Javascript与C ++进行比较要有趣得多。

如果你想要这样的代码快速运行,请展开循环(至少在C ++中)。

自从并行计算的主题出现以来,我想我会使用OpenMP添加另一个版本。当我在它的时候,我清理了一点代码,所以我可以跟踪发生了什么。我还稍微更改了时序代码,以显示整个时间而不是每次执行内部循环的时间。结果代码如下所示:

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

这里的主要补充是以下(当然有些神秘)线:

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

这告诉编译器在多个线程中执行外部循环,为每个线程分别使用inc副本,并在并行部分之后将total的各个值相加。

结果是关于你可能期望的。如果我们不使用编译器的-openmp标志启用OpenMP,则报告的时间大约是我们之前针对单个执行所看到的时间的10倍(AMD为409毫秒,英特尔为323 MS)。打开OpenMP后,AMD的时间降至217毫秒,英特尔的时间降至100毫秒。

因此,在Intel上,原始版本在外循环的一次迭代中花费了90ms。对于这个版本,我们对外循环的所有10次迭代只需稍微长一点(100 ms) - 速度提高约9:1。在具有更多内核的计算机上,我们可以期待更多的改进(OpenMP通常会自动利用所有可用的内核,但您可以根据需要手动调整线程数)。


2
投票

这是一个两极分化的话题,所以可以看看:

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

对各种语言进行基准测试。

Javascript V8等在示例中肯定能够很好地完成简单的循环,可能会产生非常相似的机器代码。对于大多数“接近用户”的应用程序来说,Javscript肯定是更好的选择,但要记住更复杂的算法/应用程序的内存浪费和不可避免的性能损失(以及缺乏控制)。

© www.soinside.com 2019 - 2024. All rights reserved.