Javascript 为什么NodeJS在计算素数和时比Rust快?

Javascript 为什么NodeJS在计算素数和时比Rust快?,javascript,node.js,rust,benchmarking,v8,Javascript,Node.js,Rust,Benchmarking,V8,我编写了一个基准测试,计算前10000个素数的总和,并将其与JavaScript进行比较。NodeJS上的JavaScript是Rust、Scala和Java中速度最快的。尽管这些程序有意使用函数式来测试素性,以显示Rust的零成本抽象的优势,但NodeJS还是击败了所有这些程序 NodeJS,一个动态类型运行时,怎么能这么快 防锈代码 fn和素数(n:usize)->u64{ 让mut primes=Vec::new(); 设mut电流:u64=2; 让mut求和:u64=0; 而primes

我编写了一个基准测试,计算前10000个素数的总和,并将其与JavaScript进行比较。NodeJS上的JavaScript是Rust、Scala和Java中速度最快的。尽管这些程序有意使用函数式来测试素性,以显示Rust的零成本抽象的优势,但NodeJS还是击败了所有这些程序

NodeJS,一个动态类型运行时,怎么能这么快

防锈代码

fn和素数(n:usize)->u64{
让mut primes=Vec::new();
设mut电流:u64=2;
让mut求和:u64=0;
而primes.len()
JavaScript代码

函数sumPrimes(n){
设素数=[];
设电流=2;
设和=0;
while(素数长度当前%p!=0)){
总和+=电流;
推动(电流);
}
++电流;
}
回报金额;
}

可以找到完整的基准。

我认为您的基准有些缺陷,因为一个足够高级的编译器可以将
sum_primes(10000)
优化为
496165411
,即使是在编译时(即)。还可以在运行时第一次调用后记忆结果,这可能就是V8的功能(尽管我希望HotSpot也会这样做)


使用编译时未知的值,而不是
10000
,例如命令行参数

答案并不简单,因为V8进行了大量的转换,但这里有一个要点:

节点的优化编译器动态地调整它使用的类型(特别是数组元素)。它能够在合适的时候使用一个字的整数(并且在收到不合适的值时对函数进行去优化)

如果我按照你的函数的原样来理解,当节点只需要1.04ms(经过一些加热后)时,Rust one需要1.28ms来计算
sum_prime(500)
。如果我将防锈代码中的
u64
更改为
u32
,则只需608µs


我使用的JavaScript代码:

函数和素数(n){
var素数=[];
无功电流=2;
var总和=0;
while(素数长度
Node可以将javascript JIT编译成本机指令。它可以应用的进一步优化将不同于其他语言应用的优化,并且在某些情况下可以更快。这个问题经常出现,答案确实是Node可以很好地优化某些类型的代码。我不同意投票结果特别是“主要基于意见”。这是基于观点的吗?解释这种行为需要对代码进行一些分析,并解释Rust和NodeJS如何优化代码。是的,也许基准测试有缺陷,但这可以在回答中得到正确的解释。@LukasKalbertodt,Re:您的编辑,如果问题中包含相关代码,那将是一件好事,但您知道什么我在编辑中添加了一个部分,排除了导致JS比生锈更快的部分,在本例中@ıııMh很好。请随意编辑问题,以包含更多代码。:@Jason:您是否在Intel CPU上运行了测试?请参阅我对Denys答案的评论:AMD CPU可能不会因为在输入数据相同的情况下对
div
指令使用不必要的大操作数而减慢速度。(与几乎所有其他ALU操作不同,div性能取决于数据,但在英特尔64位操作数大小的最佳情况下速度较慢。)我使用命令行参数进行了测试,结果没有任何变化。在JS中使用
Int32Array
是否会改善其结果?大概您和OP是在英特尔CPU上测试的,其中,64位
div
比32位
div
慢得多,即使对于相同的小除数/除数也是如此。在AMD CPU上,操作数越大,最坏情况下的吞吐量和延迟越差(因为可能有更大的输入),但最佳情况保持不变。(). 有关仅更改
div
操作数大小即可获得巨大加速的特定示例,请参阅。@PeterCordes yes。这里应该记住的不是优化糟糕算法的具体方法,也不是对整个语言性能的相关比较,但V8的优化编译器是一项令人惊叹的技术,选择一种快速语言来获得快速代码是不够的。是的,机会主义地使用更窄的类型是一种奇妙的优化,尤其是对于阵列。超前优化器(如gcc和LLVM)有时会根据值范围知识在寄存器中使用更窄的操作数大小(如
foo&0x1111
已知对于32位是安全的,即使对于
uint64\u t foo
),但并不总是如此。当它真的很重要时,我不会指望它(比如整数除法)。语言可以很快,但通常可以用任何语言编写速度较慢的代码。较低级别的语言可以让您通过了解目标uarch获得更多。