WebAssembly做矩阵运算为什么比JavaScript还慢?

UE丶沐希 阅读 4

我用Rust编译了一个简单的矩阵乘法函数到WASM,本以为会比纯JS快,结果实测反而慢了将近一倍。是不是我哪里配置错了?

数据是100×100的浮点矩阵,JS版本用的是普通的for循环,WASM那边是通过wasm-bindgen传入Float64Array。加载和调用方式应该没问题,但性能就是上不去。

#[wasm_bindgen]
pub fn matmul(a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
    let mut c = vec![0.0; n * n];
    for i in 0..n {
        for j in 0..n {
            for k in 0..n {
                c[i * n + j] += a[i * n + k] * b[k * n + j];
            }
        }
    }
    c
}
我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
Mc.海利
Mc.海利 Lv1
问题很典型,你这个情况我见过很多次了。

核心问题在于你返回了Vec,这意味着每次调用都要把WASM内存里的数据完整拷贝到JS侧。100x100的矩阵就是8万字节的数据复制,这个拷贝开销比实际计算还大。

正确的做法是预先在JS侧分配好输出数组,传给WASM函数直接写入:

#[wasm_bindgen]
pub fn matmul(a: &[f64], b: &[f64], c: &mut [f64], n: usize) {
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += a[i * n + k] * b[k * n + j];
}
c[i * n + j] = sum;
}
}
}


JS那边这样调用:

const c = new Float64Array(n * n);
wasm.matmul(a, b, c, n);
// c 就是结果,不需要拷贝


另外还有几个优化点:

第一,你的Rust代码是最朴素的三重循环,内存访问模式很差。矩阵乘法的标准优化是考虑缓存局部性,但这个属于进阶优化,先把数据拷贝问题解决了再考虑。

第二,如果追求极致性能,可以考虑用crate做循环展开或者SIMD优化,不过100x100的规模可能犯不上。

第三,wasm-bindgen默认的优化级别可能不够,编译时加opt-level = 3lto = true能提升明显。

你先试试改成传参写入的方式,性能应该能上去。
点赞
2026-03-17 00:04