为什么 Rust WASM 在计算质数时比 JavaScript 慢

10

我编写了一段非常基础的代码来计算一个数是否为质数,使用 Rust(编译为 WASM)和 JavaScript 进行算术性能基准测试。

我完全预计 Rust/WASM 会比 JavaScript 更快。在我做过的其他算术基准测试中,Rust/WASM 似乎比 JavaScript 更有优势,或者至少与之匹配。然而,在这个测试中,JavaScript 的表现似乎要比 WASM 好得多,我真的没有解释。

Rust 代码:

pub fn calculate_is_prime(number: u64) -> bool {
    if number == 1 {
        return false;
    }
    if number == 2 {
        return true;
    }
    for i in 2..number {
        if number % i == 0 {
            return false;
        }
    }
    return true;
}

#[wasm_bindgen]
pub fn bench_rs(max: u64) -> u64 {
    (1..=max).map(|n| calculate_is_prime_rs(n) as u64).sum()
}

JavaScript代码:

function calculateIsPrime(number) {
    if (number === 1) {
        return false;
    }
    if (number === 2) {
        return true;
    }
    for (let i = 2; i < number; i++) {
        if (number % i === 0) {
            return false;
        }
    }
    return true;
}

function bench_js(max) {
    let tot = 0;
    for (let n = 1; n <= max; n++) {
      tot += calculateIsPrime(n);
    }
    return tot;
}

let max = 200000;
console.log(`Amount of primes under ${max} is ${bench_js(max)}`);

基础示例项目:https://github.com/Mcluky/Stack-Overflow-Rust-Wasm-Performance-Example

我已经检查/完成的事项:

  • 在构建Rust代码时,始终设置了--release标志。
  • 在我的机器上直接运行Rust代码,比JS和WASM都快得多,因此我怀疑它与WASM目标有关。
  • 将 Rust 中的整数类型 (u64) 替换为浮点数 (f64),因为 JavaScript 使用的是浮点数,但结果相似。
  • 通过迭代测试以确保其一致性。
  • 尝试在 Rust 版本中使用 while 而不是 for-in,以防它没有像你想象的那样被优化。

1
谢谢你的提示。不幸的是,while循环并没有明显提高性能 :/ 如果它真的有这样的效果,那就太出乎意料了。 - Luke
3
这很可能是基准测试方式的问题,因此您应该提供相应的代码。例如,如果基准测试代码使用JS编写,并且您从JS中调用wasm函数,则在每次迭代中两次跨越wasm-JS边界可能会轻松超过功能本身的运行时间。 - glennsl
@T.J.Crowder "你是怎么做到的?" -> 当然,测试这个的方法可能有更好的方式,但我会做两件事。我会多次单独测试该方法,还会在给定次数的迭代中运行它,以便它运行几秒钟甚至几分钟。同一浏览器,同一系统... - Luke
2
你是如何进行基准测试的?用秒表计时吗?在脑海中默数?还是使用沙漏?而且,你得到了什么结果?不要只是说“这比那个慢”,而是要从基准测试中得出实际数字。 - Jaromanda X
我知道跨越桥梁可能会产生一些开销,这就是为什么我让它运行多达几秒甚至几分钟的原因。但是我得到的结果是JavaScript始终比另一个速度快两倍(30秒对比60秒)。你几乎可以用沙漏来测量它;) 我不认为跨越桥梁会导致如此大的差异。我在其他算术基准测试中也没有遇到这个问题...真的只有这一个测试。不幸的是,由于涉及工作,我无法分享整个代码,但稍后我可以分享一个基本要点。 - Luke
@JaromandaX 我匆忙整理了一个简单的示例(我没有费心去删除模板内容,希望这没关系):https://github.com/Mcluky/Stack-Overflow-Rust-Wasm-Performance-Example 我仍然得到了 JavaScript 大约是两倍速度的结果(请查看 orlp 的答案中我的评论)。 - Luke
1个回答

2

我无法在Windows 10上的Ryzen Threadripper 2950x上重现您的结果。我添加了以下功能:

#[wasm_bindgen]
pub fn bench_rs(max: u64) -> u64 {
    (1..=max).map(|n| calculate_is_prime_rs(n) as u64).sum()
}

function bench_js(max) {
    let tot = 0;
    for (let n = 1; n <= max; n++) {
        tot += calculateIsPrime(n);
    }
    return tot;
}

然后我使用wasm-pack build --release --target web进行编译,并在Google Chrome中进行了评估:

> console.time("rs"); console.log(bench_rs(BigInt(200000))); console.timeEnd("rs");
17984n
rs: 6015.033935546875 ms

> console.time("js"); console.log(bench_js(200000)); console.timeEnd("js");
17984
js: 6017.426025390625 ms

在Firefox中:
> console.time("rs"); console.log(bench_rs(BigInt(200000))); console.timeEnd("rs");
17984n
rs: 6076ms - timer ended

> console.time("js"); console.log(bench_js(200000)); console.timeEnd("js");
17984
js: 6074ms - timer ended

非常感谢您所做的努力来衡量性能!不幸的是,我无法复制您的结果。我使用了与您相同的设置建立了一个非常基本的项目,但我仍然得到JS平均速度几乎是Rust/WASM的两倍的结果(在我的情况下js:8s rust:15s,在linux,xeon 8260 4cores上)。我匆忙拼凑了一个示例(我没有费心去删除模板内容,希望这没关系):https://github.com/Mcluky/Stack-Overflow-Rust-Wasm-Performance-Example - Luke
@Mcluky 我已经下载并运行了你的代码,结果完全一样:https://i.imgur.com/pgDIP8v.png - orlp
1
@Mcluky - 这是我将.wasm转换为.wbt的文件,使用这个在线工具。也许如果你将你的文件转换为.wbt,比较它们可能会有所帮助。希望你能解决问题! - T.J. Crowder
1
作为 CPU 架构差异的另一个数据点,我的台式电脑采用 AMD CPU,JS 和 WASM 的时间几乎相同。然而,我的笔记本电脑采用 Intel Xeon CPU,WASM 所需的时间是 JS 的两倍。有趣的是,在 Intel CPU 上,WASM 时间变化很大,有时需要比 JS 长三倍以上的时间。JS 执行时间只有几百毫秒的差异。 - Herohtar
1
@Herohtar - 有趣。我在运行Linux的AMD Ryzen 7 PRO上得到了“基本相同”的结果。我尝试在运行Windows的Intel Core i7机器上运行测试,得到了完全不同的数字——对于JavaScript约为4600-4900ms,但Wasm则在12800-15300ms之间。 - T.J. Crowder
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接