很长一段时间以来,我一直认为C比JavaScript快。然而,今天我制作了一个基准脚本来比较两种语言的浮点计算速度,结果令人惊叹!
JavaScript似乎比C快近4倍!
我让这两种语言在我的i5-430M笔记本电脑上做同样的工作,执行了100000000次a=ab
。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;
}
即使帖子很旧,我想补充一些信息可能会很有意思。综上所述,你的测试太模糊,可能会有偏差。
在比较两种语言的速度时,首先必须精确地定义要比较它们的执行情况的上下文。
>
“幼稚”与“优化”代码:无论测试的代码是否由初学者或专家程序员编写。此参数很重要,具体取决于谁将参与您的项目。例如,在与科学家(非极客科学家)合作时,你会更多地寻找“天真”的代码性能,因为科学家不是强行的优秀程序员。
授权编译时间 :无论您是否认为您允许代码长时间构建。此参数可能很重要,具体取决于您的项目管理方法。如果你需要做自动化测试,也许用一点速度来减少编译时间可能会很有趣。另一方面,您可以考虑分发版本允许大量的构建时间。
平台可移植性:如果您的速度需要在一个或多个平台上进行比较(Windows、Linux、PS4…)
编译器/解释器可移植性:您的代码速度是否应独立于编译器/解释器。可用于多平台和/或开源项目。
其他专用参数,例如是否允许在代码中动态分配,是否要启用插件(在运行时动态加载库)等。
然后,您必须确保您的代码代表您要测试的内容
在这里,(我假设您没有用优化标志编译C),您正在测试“天真”(实际上并不那么天真)代码的快速编译速度。因为循环的大小是固定的,数据也是固定的,所以您不需要测试动态分配,而且您应该允许代码转换(下一节将对此进行详细介绍)。实际上,在这种情况下,JavaScript的性能通常比C好,因为JavaScript默认在编译时进行优化,而C编译器需要被告知进行优化。
因为我对JavaScript的了解不够,我只展示代码优化和编译类型如何在固定for循环上改变c速度,希望它能回答“JS如何显得比C快?”的问题
为此,让我们使用Matt Godbolt的C编译器资源管理器来查看gcc9.2生成的汇编代码
非优化代码
float func(){
float a(0.0);
float b(2.71);
for (int i = 0; i < 100000; ++i){
a = a + b;
}
return a;
}
编译时使用:gcc 9.2,标志-O0。生成以下程序集代码:
func():
pushq %rbp
movq %rsp, %rbp
pxor %xmm0, %xmm0
movss %xmm0, -4(%rbp)
movss .LC1(%rip), %xmm0
movss %xmm0, -12(%rbp)
movl $0, -8(%rbp)
.L3:
cmpl $99999, -8(%rbp)
jg .L2
movss -4(%rbp), %xmm0
addss -12(%rbp), %xmm0
movss %xmm0, -4(%rbp)
addl $1, -8(%rbp)
jmp .L3
.L2:
movss -4(%rbp), %xmm0
popq %rbp
ret
.LC1:
.long 1076719780
循环的代码是 “.L3“ 和 ”.L2“。为了快速起见,我们可以看到这里创建的代码根本没有优化 :进行了大量的内存访问(没有正确使用寄存器),因此有很多浪费的操作来存储和重新加载结果。
这在现代x86 CPUs上的< code>a中的FP加法的关键路径依赖链中引入了额外的5或6个周期的存储转发延迟。除了< code>addss的4或5个周期延迟之外,该函数的速度慢了两倍多。
编译器优化
同样用gcc 9.2编译的C,flag -O3。生成以下汇编代码:
func():
movss .LC1(%rip), %xmm1
movl $100000, %eax
pxor %xmm0, %xmm0
.L2:
addss %xmm1, %xmm0
subl $1, %eax
jne .L2
ret
.LC1:
.long 1076719780
代码更加简洁,并尽可能多地使用寄存器。
代码优化
编译器通常能很好地优化代码,尤其是C语言,因为代码清楚地表达了程序员想要实现的目标。这里我们希望一个固定的数学表达式尽可能快,所以让我们稍微改变一下代码。
constexpr float func(){
float a(0.0);
float b(2.71);
for (int i = 0; i < 100000; ++i){
a = a + b;
}
return a;
}
float call() {
return func();
}
我们在函数中添加了一个constexpr,告诉编译器在编译时计算它的结果。并添加了一个调用函数,以确保它将生成一些代码。
使用gcc 9.2,-O3编译,得到以下汇编代码:
call():
movss .LC0(%rip), %xmm0
ret
.LC0:
.long 1216623031
am代码很短,因为func返回的值是在编译时计算的,而call只是返回它。
当然,< code>a = b * 100000将始终编译为高效的asm,因此,如果您需要研究所有这些临时变量的FP舍入误差,只需编写重复加法循环。
通过打开优化进行快速测试,我得到了一个古老的AMD 64 X2处理器大约150毫秒的结果,一个相当新的英特尔i7处理器大约90毫秒的结果。
然后,我做了更多的工作,给出了一个您可能想要使用C的原因。我展开了四次循环html" target="_blank">迭代,得到了以下结果:
#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上运行这个版本)。然后我打开编译器的自动矢量化器(-Qpar with VC)。这将时间进一步缩短了一点,在AMD上大约40毫秒,在Intel上大约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
副本,并将并行部分之后的单个总
值相加。
结果大约是你所期望的。如果我们不使用编译器的 -openmp
标志启用 OpenMP,则报告的时间大约是我们之前看到的单个执行的 10 倍(AMD 为 409 毫秒,英特尔为 323 毫秒)。打开 OpenMP 后,AMD 的时间将降至 217 毫秒,英特尔的时钟将降至 100 毫秒。
因此,在英特尔上,原始版本的外循环一次迭代需要90毫秒。有了这个版本,我们在外循环的所有10次迭代中只需稍微长一点(100毫秒)——速度提高了大约9:1。在内核更多的机器上,我们可以期待更多的改进(OpenMP通常会自动利用所有可用的内核,尽管您可以根据需要手动调整线程数量)。
如果您使用的是Linux系统(至少在这种情况下符合POSIX),我可能会有一些坏消息。clock()
调用返回程序消耗并按 CLOCKS_PER_SEC
缩放的时钟计时周期数,即 1,000,000
。
这意味着,如果您在这样的系统上,您所说的C以微秒为单位,JavaScript以毫秒为单位(根据JS在线文档)。因此,与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
,它不是一百万的缩放因子,你需要看一些其他的东西。
第一步是确保你的代码确实被编译器优化了。这意味着,例如,为< code>gcc设置< code>-O2或< code>-O3。
在使用未优化代码的系统上,我看到:
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编写的嵌入式应用程序。
为什么比快?我使用的是CPython 3.5.2。 我试着改变我提升的幂,看看它是怎么做的,例如,如果我提升x的10或16的幂,它会从30跳到35,但如果我提升10.0作为浮动,它只是在24.1~4左右移动。 我想这和浮点转换和2次方有关,但我真的不知道。
问题内容: 示例代码在这里 问题答案: 我认为速度更快,因为使用矢量化方式和熊猫构建在此数组上。 慢,因为它使用。 操作是最快的,然后是。 请参阅此答案,并更好地解释pandas开发人员。
问题内容: 我不知道为什么numba在这里击败numpy(超过3倍)。我在这里进行基准测试时是否犯了一些根本性的错误?对于numpy来说似乎是完美的情况,不是吗?请注意,作为检查,我还运行了一个结合了numba和numpy的变体(未显示),正如预期的那样,它与不带numba的numpy运行相同。 (顺便说一下,这是一个后续问题:数字处理二维数组的最快方法:dataframe vs series v
问题内容: 如果我声明并看看,它不会给我。 因此,我必须使用以下重复(因而很糟糕)的样式构造: 例如,如果我想获得利润,是否真的需要使用它? 是我对Android或Java的误解,还是两者兼而有之? 问题答案: 我认为您对“ LayoutParams”的理解不正确。视图(或布局)必须是“父视图的LayoutParams”的实例。 例如,这是RelativeLayout中的LinearLayout。
问题内容: 为了在工作中进行演示,我想比较NodeJS和C的性能。这是我写的: Node.js(for.js): 我使用GCC编译for.c并运行它: 结果: 然后我在NodeJS中尝试了它: 结果: 在运行了无数次之后,我发现无论如何它都是成立的。如果我将for.c切换double为long在循环中使用a而不是a ,则C花费的时间甚至更长! 不是试图发动火焰战争,但是为什么执行相同操作的Node
我看了几个教程,他们所有的列表首选对话框都是这样的。 列表偏好对话框 但我的对话看起来像这样 我的对话 知道为什么我的对话框看起来不一样吗?我查看了教程,我的xml代码看起来和他们的一样。 下面是我的pref_general.xml代码。 和部分活动代码