【问题标题】:Why is my WebAssembly function slower than the JavaScript equivalent?为什么我的 WebAssembly 函数比等效的 JavaScript 慢?
【发布时间】:2018-03-02 02:12:21
【问题描述】:

对于这个广泛的问题,我们深表歉意!我正在学习 WASM,并在 C 语言中创建了 Mandelbrot 算法:

int iterateEquation(float x0, float y0, int maxiterations) {
  float a = 0, b = 0, rx = 0, ry = 0;
  int iterations = 0;
  while (iterations < maxiterations && (rx * rx + ry * ry <= 4.0)) {
    rx = a * a - b * b + x0;
    ry = 2.0 * a * b + y0;
    a = rx;
    b = ry;
    iterations++;
  }
  return iterations;
}

void mandelbrot(int *buf, float width, float height) {
  for(float x = 0.0; x < width; x++) {
    for(float y = 0.0; y < height; y++) {
      // map to mandelbrot coordinates
      float cx = (x - 150.0) / 100.0;
      float cy = (y - 75.0) / 100.0;
      int iterations = iterateEquation(cx, cy, 1000);
      int loc = ((x + y * width) * 4);
      // set the red and alpha components
      *(buf + loc) = iterations > 100 ? 255 : 0;
      *(buf + (loc+3)) = 255;
    }
  }
}

我正在编译为 WASM,如下所示(为清楚起见,省略了文件名输入/输出)

clang -emit-llvm  -O3 --target=wasm32 ...
llc -march=wasm32 -filetype=asm ...
s2wasm --initial-memory 6553600 ...
wat2wasm ... 

我正在加载JavaScript,编译,然后调用如下:

instance.exports.mandelbrot(0, 300, 150)

输出被复制到画布,这使我能够验证它是否正确执行。在我的电脑上执行上述函数大约需要 120 毫秒。

但是,这里有一个 JavaScript 等价物:

const iterateEquation = (x0, y0, maxiterations) => {
  let a = 0, b = 0, rx = 0, ry = 0;
  let iterations = 0;
  while (iterations < maxiterations && (rx * rx + ry * ry <= 4)) {
    rx = a * a - b * b + x0;
    ry = 2 * a * b + y0;
    a = rx;
    b = ry;
    iterations++;
  }
  return iterations;
}

const mandelbrot = (data) => {
  for (var x = 0; x < 300; x++) {
    for (var y = 0; y < 150; y++) {
      const cx = (x - 150) / 100;
      const cy = (y - 75) / 100;
      const res = iterateEquation(cx, cy, 1000);
      const idx = (x + y * 300) * 4;
      data[idx] = res > 100 ? 255 : 0;
      data[idx+3] = 255;
    }
  }
}

只需要大约 62 毫秒即可执行。

现在我知道 WebAssembly 是非常新的,并且没有经过非常优化。但是我还是忍不住觉得应该比这个快!

谁能发现我可能遗漏的明显东西?

另外,我的 C 代码从“0”开始直接写入内存 - 我想知道这是否安全?堆栈在分页线性内存中存储在哪里?我会冒险覆盖它吗?

这里有一个小提琴来说明:

https://wasdk.github.io/WasmFiddle/?jvoh5

运行时,它会记录两个等效实现(WASM 然后是 JavaScript)的时间

【问题讨论】:

  • 你能提供类似 jsfiddle 的链接来试用吗?你测试的是什么浏览器?您的堆栈问题已回答 here,在 WebAssembly 中使用 0 是安全的,但 C++ 在编译到 WebAssembly 时可能会不满意。
  • 我只是想让这个在 WasmFiddle 中工作,我会尽快更新问题。浏览器是 Chrome 61。感谢堆栈答案的链接。
  • @JFBastien - 我添加了一个小提琴 :-)
  • 我浏览了 C 版本,并且在任何地方都初始化了一个浮点数,我确保它具有“.0f”并且性能显着提高。通过这一更改,WebAssembly 版本比我笔记本电脑上的 JS 版本更快。但是,在我的桌面上,JS 版本仍然比 WebAssembly 版本快。修改后的小提琴:wasdk.github.io/WasmFiddle/?xbo35

标签: webassembly


【解决方案1】:

我遇到过 webassembly 很慢的情况。它变成了在编译时启用的 SAFE_HEAP 选项。去掉选项后,速度大约是native的两倍,所以编译选项也是要找的。​​p>

【讨论】:

    【解决方案2】:

    一般

    与优化的 JS 相比,通常你可以希望在繁重的数学上获得约 10% 的提升。这包括:

    • wasm 利润
    • 输入/输出内存复制费用。

    注意,Uint8Array 副本在 chrome 中的速度特别慢(在 FF 中还可以)。当您使用 rgba 数据时,最好将底层缓冲区重铸为 Uint32Array 并在其上使用 .set()

    尝试在 wasm 中按字 (rgba) 读取/写入像素的速度与读取/写入字节 (r、g、b、a) 的速度相同。我没有发现差异。

    当使用node.js 进行开发时(就像我一样),值得留在 8.2.1 进行 JS 基准测试。下一个版本将 v8 升级到 v6.0,并为此类数学引入了严重的速度回归。对于 8.2.1 - 不要使用现代 ES6 功能,如 const=&gt; 等。改用 ES5。可能是 v8 v6.2 的下一个版本将解决这些问题。

    示例 cmets

    1. 使用wasm-opt -O3,这可能会在clang -O3 之后的某个时候有所帮助。
    2. 使用s2wasm --import-memory 而不是硬编码固定内存大小
    3. 在 wasdk 站点的代码中,不要使用全局变量。当这些存在时,编译器将在内存开始时为全局分配未知块,您可能会错误地覆盖它们。
    4. 可能正确的代码应该从适当的位置添加内存副本,并且应该包含在基准测试中。您的示例不完整,恕我直言,wasdk 中的代码应该无法正常工作。
    5. 使用benchmark.js,这样更准确。

    简而言之:在继续之前,值得清理一下。

    您可能会发现挖掘https://github.com/nodeca/multimath 资源或在您的实验中使用它很有用。我专门为小型 CPU 密集型事物创建了它,以简化适当的模块初始化、内存管理、js 回退等问题。它包含“锐化掩码”实现作为示例和基准。在那里采用你的代码应该不难。

    【讨论】:

    • 说实话,很难用文字解释所有细节。检查 multimath src 会更有用。那些很小,经过实战考验并且评论很好。但是 SO 规则禁止使用单个链接回复:)
    猜你喜欢
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    • 2019-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多