为什么我的 WebAssembly 函数比 JavaScript 等效函数慢?
Why is my WebAssembly function slower than the JavaScript equivalent?
对于宽泛的问题深表歉意!我正在学习 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)
正在将输出复制到 canvas,这使我能够验证它是否正确执行。在我的电脑上执行上述函数大约需要 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”开始的内存 - 我想知道这是否安全?堆栈存储在分页线性内存中的什么位置?我要冒险覆盖它吗?
这里有一个fiddle来说明:
https://wasdk.github.io/WasmFiddle/?jvoh5
当 运行 时,它记录两个等效实现的时间(WASM 然后 JavaScript)
一般
与优化的 JS 相比,通常您可以希望在繁重的数学运算上获得约 10% 的提升。其中包括:
- wasm 利润
- in/out 内存拷贝开销。
注意,Uint8Array 复制在 chrome 中特别慢(在 FF 中正常)。当您使用 rgba 数据时,最好将底层缓冲区重新转换为 Uint32Array 蚂蚁,并在其上使用 .set()
。
尝试在 wasm 中按单词 (rgba) read/write 像素的工作速度与 read/write 字节 (r, g, b, a) 相同。我没有发现不同。
当使用 node.js
进行开发时(就像我一样),值得留在 8.2.1 上进行 JS 基准测试。下一个版本将 v8 升级到 v6.0 并为此类数学引入了严重的速度回归。对于 8.2.1 - 不要使用 const
、=>
等现代 ES6 功能。改用 ES5。 v8 v6.2 的下一个版本可能会解决这些问题。
示例评论
- 使用
wasm-opt -O3
,这可能会在 clang -O3
之后的某个时候有所帮助。
- 使用
s2wasm --import-memory
而不是硬编码固定内存大小
- 在 wasdk 站点的代码中,不要使用全局变量。当它们存在时,编译器将在内存开始处为全局变量分配未知块,您可以错误地覆盖它们。
- 可能,正确的代码应该从适当的位置添加内存副本,并且应该包含在基准测试中。您的样本不完整,来自 wasdk 的恕我直言代码应该无法正常工作。
- 使用
benchmark.js
,更精确。
简而言之:在继续之前,清理一些东西是值得的。
您可能会发现挖掘 https://github.com/nodeca/multimath 资源很有用,或者在您的实验中使用它。我专门为小型 CPU 密集型事物创建了它,以简化适当模块初始化、内存管理、js 回退等问题。它包含 'unsharp mask' 实施作为示例和基准。在那里采用你的代码应该不难。
我遇到过 webassembly 运行缓慢的情况。它变成了编译时启用的 SAFE_HEAP 选项。去掉选项后,速度是原生的两倍左右,所以编译选项也是要看的。
Google Chromes 的“检查”window 似乎使 WebAssembly 减慢了约 100%。因此,对于基准测试,您应该使用“警报”而不是使用“console.log”来显示结果。或者,在 MacOS Safari 中进行基准测试,这似乎不会降低 WebAssembly 的速度。 (我还没有尝试过 MS Edge。)
将 WebStorm 等外部调试器链接到 Chrome 也会降低速度。
对于宽泛的问题深表歉意!我正在学习 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)
正在将输出复制到 canvas,这使我能够验证它是否正确执行。在我的电脑上执行上述函数大约需要 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”开始的内存 - 我想知道这是否安全?堆栈存储在分页线性内存中的什么位置?我要冒险覆盖它吗?
这里有一个fiddle来说明:
https://wasdk.github.io/WasmFiddle/?jvoh5
当 运行 时,它记录两个等效实现的时间(WASM 然后 JavaScript)
一般
与优化的 JS 相比,通常您可以希望在繁重的数学运算上获得约 10% 的提升。其中包括:
- wasm 利润
- in/out 内存拷贝开销。
注意,Uint8Array 复制在 chrome 中特别慢(在 FF 中正常)。当您使用 rgba 数据时,最好将底层缓冲区重新转换为 Uint32Array 蚂蚁,并在其上使用 .set()
。
尝试在 wasm 中按单词 (rgba) read/write 像素的工作速度与 read/write 字节 (r, g, b, a) 相同。我没有发现不同。
当使用 node.js
进行开发时(就像我一样),值得留在 8.2.1 上进行 JS 基准测试。下一个版本将 v8 升级到 v6.0 并为此类数学引入了严重的速度回归。对于 8.2.1 - 不要使用 const
、=>
等现代 ES6 功能。改用 ES5。 v8 v6.2 的下一个版本可能会解决这些问题。
示例评论
- 使用
wasm-opt -O3
,这可能会在clang -O3
之后的某个时候有所帮助。 - 使用
s2wasm --import-memory
而不是硬编码固定内存大小 - 在 wasdk 站点的代码中,不要使用全局变量。当它们存在时,编译器将在内存开始处为全局变量分配未知块,您可以错误地覆盖它们。
- 可能,正确的代码应该从适当的位置添加内存副本,并且应该包含在基准测试中。您的样本不完整,来自 wasdk 的恕我直言代码应该无法正常工作。
- 使用
benchmark.js
,更精确。
简而言之:在继续之前,清理一些东西是值得的。
您可能会发现挖掘 https://github.com/nodeca/multimath 资源很有用,或者在您的实验中使用它。我专门为小型 CPU 密集型事物创建了它,以简化适当模块初始化、内存管理、js 回退等问题。它包含 'unsharp mask' 实施作为示例和基准。在那里采用你的代码应该不难。
我遇到过 webassembly 运行缓慢的情况。它变成了编译时启用的 SAFE_HEAP 选项。去掉选项后,速度是原生的两倍左右,所以编译选项也是要看的。
Google Chromes 的“检查”window 似乎使 WebAssembly 减慢了约 100%。因此,对于基准测试,您应该使用“警报”而不是使用“console.log”来显示结果。或者,在 MacOS Safari 中进行基准测试,这似乎不会降低 WebAssembly 的速度。 (我还没有尝试过 MS Edge。)
将 WebStorm 等外部调试器链接到 Chrome 也会降低速度。