为什么我的WebAssembly功能比JavaScript等效的慢?

时间:2017-09-20 21:10:35

标签: webassembly

为广泛的问题道歉!我正在学习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)

正在将输出复制到画布,这使我能够验证它是否正确执行。在我的计算机上,上述功能需要大约120ms才能执行。

但是,这是一个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;
    }
  }
}

执行只需约62ms。

现在我知道WebAssembly是非常新的,并没有非常优化。但我无法感觉它应该比这更快!

有人能发现一些我可能错过的明显事物吗?

此外,我的C代码直接写入内存,从&#39; 0&#39; - 我想知道这是否安全?堆栈存储在分页线性存储器中的哪个位置?我是否会冒险覆盖它?

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

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

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

2 个答案:

答案 0 :(得分:2)

一般

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

  • wasm profit
  • 输入/输出记忆复制费用。

注意,Uint8Array副本的chrome特别慢(在FF中可以)。当您使用rgba数据时,最好将基础缓冲区重新转换为Uint32Array,并在其上使用tail

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

当使用.set()进行开发时(和我一样),对于JS基准测试来说,值得保持8.2.1。下一版本将v8升级到v6.0,并为此类数学引入了严重的速度回归。对于8.2.1 - 请勿使用现代ES6功能,例如node.jsconst等。请改用ES5。可能是v8 v6.2的下一个版本将解决这些问题。

样本评论

  1. 使用=>,这可能会在wasm-opt -O3之后的某个时间提供帮助。
  2. 使用clang -O3代替硬编码固定内存大小
  3. 在wasdk网站的代码中,请勿使用全局变量。当那些存在时,编译器将在内存开始时为全局变量分配未知块,并且你可以错误地覆盖它们。
  4. 可能正确的代码应该从适当的位置添加内存副本,并且应该包含在基准测试中。您的样本不完整,来自wasdk的IMHO代码不能正常工作。
  5. 使用s2wasm --import-memory,更准确。
  6. 简而言之:在继续之前,值得清理一切。

    您可能会觉得挖掘https://github.com/nodeca/multimath来源很有用,或者在实验中使用它。我专门为小型CPU密集型产品创建它,以简化正确的模块初始化,内存管理,js后备等问题。它包含'非锐化掩码'实现作为示例和基准。在那里采用你的代码应该不难。

答案 1 :(得分:1)

我遇到了Webassembly运行缓慢的情况。编译时启用了SAFE_HEAP选项。删除选项后,速度大约是本地速度的两倍,因此也要寻找编译选项。