为什么 NodeJS 在计算素数和方面比 Rust 快?

Why is NodeJS faster than Rust in computing the sum of the primes?

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

动态类型运行时 NodeJS 怎么能这么快?

Rust 代码

fn sum_primes(n: usize) -> u64 {
    let mut primes = Vec::new();
    let mut current: u64 = 2;
    let mut sum: u64 = 0;

    while primes.len() < n {
        if primes.iter().all(|p| current % p != 0) {
            sum += current;
            primes.push(current);
        }
        current += 1;
    }
    sum
}

Java脚本代码

function sumPrimes(n) {
    let primes = [];
    let current = 2;
    let sum = 0;
    while (primes.length < n) {
        if (primes.every(p => current % p != 0)) {
            sum += current;
            primes.push(current);
        }
        ++current;
    }
    return sum;
}

可以找到完整的基准测试 on GitHub

我认为您的基准测试存在一些缺陷,因为足够先进的编译器甚至可以在编译时(即 Prepack, Closure)将 sum_primes(10000) 优化为 496165411。也可以在 运行 时间第一次调用后记忆结果,这可能就是 V8 所做的(尽管我希望 HotSpot 做同样的事情)。

使用编译时未知的值代替 10000,例如命令行参数。

答案并不简单,因为 V8 进行了很多 次转换,但这里有一个重点:

Node 的优化编译器动态调整它使用的类型(尤其是数组元素)。它能够在适合时使用单字整数(并且 取消优化 接收到不适合值时的函数)。

如果我按原样使用你的函数,Rust 需要 1.28 毫秒来计算 sum_prime(500),而 Node 只需要 1.04 毫秒(经过一些预热)。如果我将 Rust 代码中的 u64 更改为 u32,那么它只需要 608µs。


我使用的JavaScript代码:

function sum_primes(n) {
    var primes = [];
    var current = 2;
    var sum = 0;
    while (primes.length < n) {
        if (primes.every(function (p) { return current % p != 0; })) {
            sum += current;
            primes.push(current);
        }
        ++current;
    }
    return sum;
}
console.log(sum_primes(200));
// some warming
for (let i=0; i<100; i++) sum_primes(100);
console.time("primes");
console.log(sum_primes(500));
console.timeEnd("primes");

这个 JavaScript 代码比你的 Rust 代码快,但比这个慢:

use std::time::Instant;

fn sum_primes(n: usize) -> u32 {
    let mut primes = Vec::new();
    let mut current: u32 = 2;
    let mut sum: u32 = 0;

    while primes.len() < n {
        if primes.iter().all(|p| current % p != 0) {
            sum += current;
            primes.push(current);
        }
        current += 1;
    }
    sum
}

fn main() {
    println!("{}", sum_primes(200));
    let s = Instant::now();
    println!("{}", sum_primes(500));
    println!("duration: {:?}", s.elapsed());
}