为什么 webAssembly 函数比相同的 JS 函数慢近 300 倍
Why is webAssembly function almost 300 time slower than same JS function
查找行长度 300* 较慢
首先我阅读了
的答案
但它并没有说明问题,我已经投入了很多时间,很可能是靠在墙上的黄色东西。
我不使用全局变量,我不使用任何内存。我有两个简单的函数,可以找到线段的长度并将它们与普通旧 Javascript 中的相同内容进行比较。我有 4 个参数,另外 3 个本地参数和 returns 浮点数或双精度数。
在 Chrome 上 Javascript 比 webAssembly 快 40 倍,在 firefox 上 wasm 几乎比 [=160= 慢 300 倍].
jsPref 测试用例。
我已经在 jsPref 添加了一个测试用例 WebAssembly V Javascript math
我做错了什么?
两者都
- 我错过了一个明显的错误、错误的做法,或者我正在忍受编码员的愚蠢。
- WebAssembly 不适用于 32 位 OS(win 10 笔记本电脑 i7CPU)
- WebAssembly 远非现成的技术。
请选择选项1。
我已阅读 webAssembly use case
Re-use existing code by targeting WebAssembly, embedded in a larger
JavaScript / HTML application. This could be anything from simple
helper libraries, to compute-oriented task offload.
我希望我可以用 webAssembly 替换一些几何库以获得一些额外的性能。我希望它会很棒,比如快 10 倍或更多倍。但是 WTF 慢了 300 倍。
更新
这不是 JS 优化问题。
为确保优化的影响尽可能小,我使用以下方法进行了测试,以减少或消除任何优化偏差..
- 计数器
c += length(...
以确保所有代码都被执行。
bigCount += c
以确保执行整个功能。不需要
- 每个函数 4 行以减少内联偏差。不需要
- 所有值都是随机生成的双精度值
- 每个函数调用return都有不同的结果。
- 使用
Math.hypot
在 JS 中添加较慢的长度计算以证明代码正在 运行.
- 添加空调用return第一个参数 JS 以查看开销
// setup and associated functions
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const a = setOf(100009,i=>rand(-100000,100000));
var bigCount = 0;
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
function lenSlow(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.hypot(nx,ny);
}
function lenEmpty(x,y,x1,y1){
return x;
}
// Test functions in same scope as above. None is in global scope
// Each function is copied 4 time and tests are performed randomly.
// c += length(... to ensure all code is executed.
// bigCount += c to ensure whole function is executed.
// 4 lines for each function to reduce a inlining skew
// all values are randomly generated doubles
// each function call returns a different result.
tests : [{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += length(a1,a2,a3,a4);
c += length(a2,a3,a4,a1);
c += length(a3,a4,a1,a2);
c += length(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length64",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lengthF(a1,a2,a3,a4);
c += lengthF(a2,a3,a4,a1);
c += lengthF(a3,a4,a1,a2);
c += lengthF(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length32",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += len(a1,a2,a3,a4);
c += len(a2,a3,a4,a1);
c += len(a3,a4,a1,a2);
c += len(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length JS",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenSlow(a1,a2,a3,a4);
c += lenSlow(a2,a3,a4,a1);
c += lenSlow(a3,a4,a1,a2);
c += lenSlow(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Length JS Slow",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenEmpty(a1,a2,a3,a4);
c += lenEmpty(a2,a3,a4,a1);
c += lenEmpty(a3,a4,a1,a2);
c += lenEmpty(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Empty",
}
],
更新结果。
因为测试中有更多的开销,所以结果更接近,但 JS 代码仍然快两个数量级。
注意函数 Math.hypo
t 有多慢。如果优化有效,该函数将接近更快的 len
函数。
- WebAssembly 13389µs
- Javascript 728µs
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 147
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 12736µs ±69µs (*) 3013 samples
---------------------------------------------
Test : 'length32'
Mean : 13389µs ±94µs (*) 2914 samples
---------------------------------------------
Test : 'length JS'
Mean : 728µs ±6µs (*) 2906 samples
---------------------------------------------
Test : 'Length JS Slow'
Mean : 23374µs ±191µs (*) 2939 samples << This function use Math.hypot
rather than Math.sqrt
---------------------------------------------
Test : 'Empty'
Mean : 79µs ±2µs (*) 2928 samples
-All ----------------------------------------
Mean : 10.097ms Totals time : 148431.200ms 14700 samples
(*) Error rate approximation does not represent the variance.
*/
如果 WebAssambly 不优化,它还有什么意义
更新结束
与问题相关的所有内容。
求直线的长度。
自定义语言的原始来源
// declare func the < indicates export name, the param with types and return type
func <lengthF(float x, float y, float x1, float y1) float {
float nx, ny, dist; // declare locals float is f32
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
// and as double
func <length(double x, double y, double x1, double y1) double {
double nx, ny, dist;
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
代码编译为 Wat 以供校对
(module
(func
(export "lengthF")
(param f32 f32 f32 f32)
(result f32)
(local f32 f32 f32)
get_local 2
get_local 0
f32.sub
set_local 4
get_local 3
get_local 1
f32.sub
tee_local 5
get_local 5
f32.mul
get_local 4
get_local 4
f32.mul
f32.add
f32.sqrt
)
(func
(export "length")
(param f64 f64 f64 f64)
(result f64)
(local f64 f64 f64)
get_local 2
get_local 0
f64.sub
set_local 4
get_local 3
get_local 1
f64.sub
tee_local 5
get_local 5
f64.mul
get_local 4
get_local 4
f64.mul
f64.add
f64.sqrt
)
)
作为十六进制字符串编译的 wasm(注意不包括名称部分)并使用 WebAssembly.compile 加载。然后导出函数 运行 针对 Javascript 函数 len(在下面的代码片段中)
// hex of above without the name section
const asm = `0061736d0100000001110260047d7d7d7d017d60047c7c7c7c017c0303020001071402076c656e677468460000066c656e67746800010a3b021c01037d2002200093210420032001932205200594200420049492910b1c01037c20022000a1210420032001a122052005a220042004a2a09f0b`
const bin = new Uint8Array(asm.length >> 1);
for(var i = 0; i < asm.length; i+= 2){ bin[i>>1] = parseInt(asm.substr(i,2),16) }
var length,lengthF;
WebAssembly.compile(bin).then(module => {
const wasmInstance = new WebAssembly.Instance(module, {});
lengthF = wasmInstance.exports.lengthF;
length = wasmInstance.exports.length;
});
// test values are const (same result if from array or literals)
const a1 = rand(-100000,100000);
const a2 = rand(-100000,100000);
const a3 = rand(-100000,100000);
const a4 = rand(-100000,100000);
// javascript version of function
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
所有 3 个函数的测试代码都相同,运行 在严格模式下。
tests : [{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
length(a1,a2,a3,a4);
}
},
name : "length64",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
lengthF(a1,a2,a3,a4);
}
},
name : "length32",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
len(a1,a2,a3,a4);
}
},
name : "lengthNative",
}
]
FireFox 上的测试结果是
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 34
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 26359µs ±128µs (*) 1128 samples
---------------------------------------------
Test : 'length32'
Mean : 27456µs ±109µs (*) 1144 samples
---------------------------------------------
Test : 'lengthNative'
Mean : 106µs ±2µs (*) 1128 samples
-All ----------------------------------------
Mean : 18.018ms Totals time : 61262.240ms 3400 samples
(*) Error rate approximation does not represent the variance.
*/
JS引擎可以对这个例子应用很多动态优化:
用整数执行所有计算,只在最后调用 Math.sqrt.
时转换为双精度
内联调用 len
函数。
将计算提升到循环之外,因为它总是计算相同的东西。
认识到循环是空的并完全消除它。
认识到测试函数永远不会返回结果,因此删除整个测试函数。
即使您将每次调用的结果相加,除 (4) 之外的所有内容都适用。对于 (5),无论哪种方式,最终结果都是一个空函数。
使用 Wasm 引擎无法完成这些步骤中的大部分,因为它无法跨语言边界内联(至少现在没有引擎这样做,AFAICT)。此外,对于 Wasm,假定生产(离线)编译器已经执行了相关的优化,因此 Wasm JIT 往往不如 JavaScript 那样激进,后者不可能进行静态优化。
Andreas 描述了 JavaScript 实现 的一些充分理由。但是,您的代码还有许多其他问题。
- 这是一个经典的 'micro benchmark',即您正在测试的代码非常小,测试循环中的其他开销是一个重要因素。例如,从 JavaScript 调用 WebAssembly 会产生开销,这将影响您的结果。你想测量什么?原始处理速度?还是语言边界的开销?
- 由于测试代码的微小变化,您的结果差异很大,从 x300 到 x2。同样,这是一个微基准问题。其他人在使用这种方法衡量性能时也看到了同样的情况,例如this post claims wasm is x84 faster,这显然是错误的!
- 当前的 WebAssembly VM 非常新,而且是 MVP。它会变得更快。您的 JavaScript 虚拟机用了 20 年才达到目前的速度。 JS <=> wasm 边界的性能是 worked on and optimised right now.
要获得更明确的答案,请参阅 WebAssembly 团队的联合论文,其中概述了预期的 runtime performance gain of around 30%
最后,回答你的问题:
Whats the point of WebAssembly if it does not optimise
我认为您对 WebAssembly 将为您做什么有误解。根据上面的论文,运行时性能优化非常适度。但是,仍然有一些性能优势:
- 其紧凑的二进制格式意味着低级性质意味着浏览器可以比 JavaScript 更快地加载、解析和编译代码。预计 WebAssembly 的编译速度会比浏览器下载它的速度快。
- WebAssembly 具有可预测的运行时性能。随着 JavaScript 的进一步优化,性能通常会随着每次迭代而提高。由于 se 优化,它也可以减少。
还有一些与性能无关的优点。
如需更真实的性能衡量,请查看:
- 是use within Figma
- 与 PDFKit
一起使用的结果
两者都是实用的生产代码库。
认真回答
好像
- WebAssembly is far from a ready technology.
实际上确实在这方面发挥了作用,以及在 Firefox 中从 JS 调用 WASM 的性能 was improved in late 2018。
运行 当前 FF/Chromium 中的基准测试产生的结果类似于“从 JS 调用 WASM 实现比从 JS 调用 JS 实现慢 4-10 倍”。尽管如此,引擎似乎并没有跨 WASM/JS 边界内联,并且必须调用与不必调用的开销是显着的(正如其他答案已经指出的那样)。
嘲讽回答
你的基准都是错误的。事实证明,JS 实际上比 WASM 慢了 8-40 倍(FF,Chrome)。 WTF,JS 太懒了。
我打算证明这一点吗?当然(不是)。
首先,我在 C:
中重新实现了您的基准测试代码
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static double lengthC(double x, double y, double x1, double y1) {
double nx = x1 - x;
double ny = y1 - y;
return sqrt(nx * nx + ny * ny);
}
double lengthArrayC(double* a, size_t length) {
double c = 0;
for (size_t i = 0; i < length; i++) {
double a1 = a[i + 0];
double a2 = a[i + 1];
double a3 = a[i + 2];
double a4 = a[i + 3];
c += lengthC(a1,a2,a3,a4);
c += lengthC(a2,a3,a4,a1);
c += lengthC(a3,a4,a1,a2);
c += lengthC(a4,a1,a2,a3);
}
return c;
}
#ifdef __wasm__
__attribute__((import_module("js"), import_name("len")))
double lengthJS(double x, double y, double x1, double y1);
double lengthArrayJS(double* a, size_t length) {
double c = 0;
for (size_t i = 0; i < length; i++) {
double a1 = a[i + 0];
double a2 = a[i + 1];
double a3 = a[i + 2];
double a4 = a[i + 3];
c += lengthJS(a1,a2,a3,a4);
c += lengthJS(a2,a3,a4,a1);
c += lengthJS(a3,a4,a1,a2);
c += lengthJS(a4,a1,a2,a3);
}
return c;
}
__attribute__((import_module("bench"), import_name("now")))
double now();
__attribute__((import_module("bench"), import_name("result")))
void printtime(int benchidx, double ns);
#else
void printtime(int benchidx, double ns) {
if (benchidx == 1) {
printf("C: %f ns\n", ns);
} else if (benchidx == 0) {
printf("avoid the optimizer: %f\n", ns);
} else {
fprintf(stderr, "Unknown benchmark: %d", benchidx);
exit(-1);
}
}
double now() {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
} else {
return sqrt(-1);
}
}
#endif
#define iters 1000000
double a[iters+3];
int main() {
int bigCount = 0;
srand(now());
for (size_t i = 0; i < iters + 3; i++)
a[i] = (double)rand()/RAND_MAX*2e5-1e5;
for (int i = 0; i < 10; i++) {
double startTime, endTime;
double c;
startTime = now();
c = lengthArrayC(a, iters);
endTime = now();
bigCount = (bigCount + (int64_t)c) % 1000;
printtime(1, (endTime - startTime) * 1e9 / iters / 4);
#ifdef __wasm__
startTime = now();
c = lengthArrayJS(a, iters);
endTime = now();
bigCount = (bigCount + (int64_t)c) % 1000;
printtime(2, (endTime - startTime) * 1e9 / iters / 4);
#endif
}
printtime(0, bigCount);
return 0;
}
用 clang 12.0.1 编译:
clang -O3 -target wasm32-wasi --sysroot /opt/wasi-sdk/wasi-sysroot/ foo2.c -o foo2.wasm
并通过导入从 JS 为其提供长度函数:
"use strict";
(async (wasm) => {
const wasmbytes = new Uint8Array(wasm.length);
for (var i in wasm)
wasmbytes[i] = wasm.charCodeAt(i);
(await WebAssembly.instantiate(wasmbytes, {
js: {
len: function (x,y,x1,y1) {
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
},
bench: {
now: () => window.performance.now() / 1e3,
result: (bench, ns) => {
let name;
if (bench == 1) { name = "C" }
else if (bench == 2) { name = "JS" }
else if (bench == 0) { console.log("Optimizer confuser: " + ns); /*not really necessary*/; return; }
else { throw "unknown bench"; }
console.log(name + ": " + ns + " ns");
},
},
})).instance.exports._start();
})(atob('AGFzbQEAAAABFQRgBHx8fHwBfGAAAXxgAn98AGAAAAIlAwJqcwNsZW4AAAViZW5jaANub3cAAQViZW5jaAZyZXN1bHQAAgMCAQMFAwEAfAcTAgZtZW1vcnkCAAZfc3RhcnQAAwr2BAHzBAMIfAJ/An5BmKzoAwJ/EAEiA0QAAAAAAADwQWMgA0QAAAAAAAAAAGZxBEAgA6sMAQtBAAtBAWutNwMAQejbl3whCANAQZis6ANBmKzoAykDAEKt/tXk1IX9qNgAfkIBfCIKNwMAIAhBmKzoA2ogCkIhiKe3RAAAwP///99Bo0QAAAAAAGoIQaJEAAAAAABq+MCgOQMAIAhBCGoiCA0ACwNAEAEhBkGQCCsDACEBQYgIKwMAIQRBgAgrAwAhAEQAAAAAAAAAACECQRghCANAIAQhAyABIgQgAKEiASABoiIHIAMgCEGACGorAwAiAaEiBSAFoiIFoJ8gACAEoSIAIACiIgAgBaCfIAAgASADoSIAIACiIgCgnyACIAcgAKCfoKCgoCECIAMhACAIQQhqIghBmKToA0cNAAtBARABIAahRAAAAABlzc1BokQAAAAAgIQuQaNEAAAAAAAA0D+iEAICfiACmUQAAAAAAADgQ2MEQCACsAwBC0KAgICAgICAgIB/CyALfEQAAAAAAAAAACECQYDcl3whCBABIQMDQCACIAhBgKzoA2orAwAiBSAIQYis6ANqKwMAIgEgCEGQrOgDaisDACIAIAhBmKzoA2orAwAiBBAAoCABIAAgBCAFEACgIAAgBCAFIAEQAKAgBCAFIAEgABAAoCECIAhBCGoiCA0AC0ECEAEgA6FEAAAAAGXNzUGiRAAAAACAhC5Bo0QAAAAAAADQP6IQAkLoB4EhCgJ+IAKZRAAAAAAAAOBDYwRAIAKwDAELQoCAgICAgICAgH8LIAp8QugHgSELIAlBAWoiCUEKRw0AC0EAIAuntxACCwB2CXByb2R1Y2VycwEMcHJvY2Vzc2VkLWJ5AQVjbGFuZ1YxMS4wLjAgKGh0dHBzOi8vZ2l0aHViLmNvbS9sbHZtL2xsdm0tcHJvamVjdCAxNzYyNDliZDY3MzJhODA0NGQ0NTcwOTJlZDkzMjc2ODcyNGE2ZjA2KQ=='))
现在,毫不奇怪,从 WASM 调用 JS 函数比从 WASM 调用 WASM 函数慢很多。 (实际上,WASM→WASM 并没有调用。你可以看到 f64.sqrt
被内联到 _start
。)
(最后一个有趣的数据点是 WASM→WASM 和 JS→JS 似乎具有相同的成本(在我的 E3-1280 上每个内联 length(…)
大约 1.5 ns)。免责声明:完全有可能我的基准比原来的问题更糟糕。)
结论
WASM 并不慢,越界才是。现在和可预见的未来,除非是重要的计算任务,否则不要将东西放入 WASM。 (即便如此,这也取决于。有时候,JS 引擎真的很聪明。有时候。)
查找行长度 300* 较慢
首先我阅读了
但它并没有说明问题,我已经投入了很多时间,很可能是靠在墙上的黄色东西。
我不使用全局变量,我不使用任何内存。我有两个简单的函数,可以找到线段的长度并将它们与普通旧 Javascript 中的相同内容进行比较。我有 4 个参数,另外 3 个本地参数和 returns 浮点数或双精度数。
在 Chrome 上 Javascript 比 webAssembly 快 40 倍,在 firefox 上 wasm 几乎比 [=160= 慢 300 倍].
jsPref 测试用例。
我已经在 jsPref 添加了一个测试用例 WebAssembly V Javascript math
我做错了什么?
两者都
- 我错过了一个明显的错误、错误的做法,或者我正在忍受编码员的愚蠢。
- WebAssembly 不适用于 32 位 OS(win 10 笔记本电脑 i7CPU)
- WebAssembly 远非现成的技术。
请选择选项1。
我已阅读 webAssembly use case
Re-use existing code by targeting WebAssembly, embedded in a larger JavaScript / HTML application. This could be anything from simple helper libraries, to compute-oriented task offload.
我希望我可以用 webAssembly 替换一些几何库以获得一些额外的性能。我希望它会很棒,比如快 10 倍或更多倍。但是 WTF 慢了 300 倍。
更新
这不是 JS 优化问题。
为确保优化的影响尽可能小,我使用以下方法进行了测试,以减少或消除任何优化偏差..
- 计数器
c += length(...
以确保所有代码都被执行。 bigCount += c
以确保执行整个功能。不需要- 每个函数 4 行以减少内联偏差。不需要
- 所有值都是随机生成的双精度值
- 每个函数调用return都有不同的结果。
- 使用
Math.hypot
在 JS 中添加较慢的长度计算以证明代码正在 运行. - 添加空调用return第一个参数 JS 以查看开销
// setup and associated functions
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const a = setOf(100009,i=>rand(-100000,100000));
var bigCount = 0;
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
function lenSlow(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.hypot(nx,ny);
}
function lenEmpty(x,y,x1,y1){
return x;
}
// Test functions in same scope as above. None is in global scope
// Each function is copied 4 time and tests are performed randomly.
// c += length(... to ensure all code is executed.
// bigCount += c to ensure whole function is executed.
// 4 lines for each function to reduce a inlining skew
// all values are randomly generated doubles
// each function call returns a different result.
tests : [{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += length(a1,a2,a3,a4);
c += length(a2,a3,a4,a1);
c += length(a3,a4,a1,a2);
c += length(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length64",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lengthF(a1,a2,a3,a4);
c += lengthF(a2,a3,a4,a1);
c += lengthF(a3,a4,a1,a2);
c += lengthF(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length32",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += len(a1,a2,a3,a4);
c += len(a2,a3,a4,a1);
c += len(a3,a4,a1,a2);
c += len(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "length JS",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenSlow(a1,a2,a3,a4);
c += lenSlow(a2,a3,a4,a1);
c += lenSlow(a3,a4,a1,a2);
c += lenSlow(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Length JS Slow",
},{
func : function (){
var i,c=0,a1,a2,a3,a4;
for (i = 0; i < 10000; i += 1) {
a1 = a[i];
a2 = a[i+1];
a3 = a[i+2];
a4 = a[i+3];
c += lenEmpty(a1,a2,a3,a4);
c += lenEmpty(a2,a3,a4,a1);
c += lenEmpty(a3,a4,a1,a2);
c += lenEmpty(a4,a1,a2,a3);
}
bigCount = (bigCount + c) % 1000;
},
name : "Empty",
}
],
更新结果。
因为测试中有更多的开销,所以结果更接近,但 JS 代码仍然快两个数量级。
注意函数 Math.hypo
t 有多慢。如果优化有效,该函数将接近更快的 len
函数。
- WebAssembly 13389µs
- Javascript 728µs
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 147
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 12736µs ±69µs (*) 3013 samples
---------------------------------------------
Test : 'length32'
Mean : 13389µs ±94µs (*) 2914 samples
---------------------------------------------
Test : 'length JS'
Mean : 728µs ±6µs (*) 2906 samples
---------------------------------------------
Test : 'Length JS Slow'
Mean : 23374µs ±191µs (*) 2939 samples << This function use Math.hypot
rather than Math.sqrt
---------------------------------------------
Test : 'Empty'
Mean : 79µs ±2µs (*) 2928 samples
-All ----------------------------------------
Mean : 10.097ms Totals time : 148431.200ms 14700 samples
(*) Error rate approximation does not represent the variance.
*/
如果 WebAssambly 不优化,它还有什么意义
更新结束
与问题相关的所有内容。
求直线的长度。
自定义语言的原始来源
// declare func the < indicates export name, the param with types and return type
func <lengthF(float x, float y, float x1, float y1) float {
float nx, ny, dist; // declare locals float is f32
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
// and as double
func <length(double x, double y, double x1, double y1) double {
double nx, ny, dist;
nx = x1 - x;
ny = y1 - y;
dist = sqrt(ny * ny + nx * nx);
return dist;
}
代码编译为 Wat 以供校对
(module
(func
(export "lengthF")
(param f32 f32 f32 f32)
(result f32)
(local f32 f32 f32)
get_local 2
get_local 0
f32.sub
set_local 4
get_local 3
get_local 1
f32.sub
tee_local 5
get_local 5
f32.mul
get_local 4
get_local 4
f32.mul
f32.add
f32.sqrt
)
(func
(export "length")
(param f64 f64 f64 f64)
(result f64)
(local f64 f64 f64)
get_local 2
get_local 0
f64.sub
set_local 4
get_local 3
get_local 1
f64.sub
tee_local 5
get_local 5
f64.mul
get_local 4
get_local 4
f64.mul
f64.add
f64.sqrt
)
)
作为十六进制字符串编译的 wasm(注意不包括名称部分)并使用 WebAssembly.compile 加载。然后导出函数 运行 针对 Javascript 函数 len(在下面的代码片段中)
// hex of above without the name section
const asm = `0061736d0100000001110260047d7d7d7d017d60047c7c7c7c017c0303020001071402076c656e677468460000066c656e67746800010a3b021c01037d2002200093210420032001932205200594200420049492910b1c01037c20022000a1210420032001a122052005a220042004a2a09f0b`
const bin = new Uint8Array(asm.length >> 1);
for(var i = 0; i < asm.length; i+= 2){ bin[i>>1] = parseInt(asm.substr(i,2),16) }
var length,lengthF;
WebAssembly.compile(bin).then(module => {
const wasmInstance = new WebAssembly.Instance(module, {});
lengthF = wasmInstance.exports.lengthF;
length = wasmInstance.exports.length;
});
// test values are const (same result if from array or literals)
const a1 = rand(-100000,100000);
const a2 = rand(-100000,100000);
const a3 = rand(-100000,100000);
const a4 = rand(-100000,100000);
// javascript version of function
function len(x,y,x1,y1){
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
所有 3 个函数的测试代码都相同,运行 在严格模式下。
tests : [{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
length(a1,a2,a3,a4);
}
},
name : "length64",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
lengthF(a1,a2,a3,a4);
}
},
name : "length32",
},{
func : function (){
var i;
for (i = 0; i < 100000; i += 1) {
len(a1,a2,a3,a4);
}
},
name : "lengthNative",
}
]
FireFox 上的测试结果是
/*
=======================================
Performance test. : WebAssm V Javascript
Use strict....... : true
Data view........ : false
Duplicates....... : 4
Cycles........... : 34
Samples per cycle : 100
Tests per Sample. : undefined
---------------------------------------------
Test : 'length64'
Mean : 26359µs ±128µs (*) 1128 samples
---------------------------------------------
Test : 'length32'
Mean : 27456µs ±109µs (*) 1144 samples
---------------------------------------------
Test : 'lengthNative'
Mean : 106µs ±2µs (*) 1128 samples
-All ----------------------------------------
Mean : 18.018ms Totals time : 61262.240ms 3400 samples
(*) Error rate approximation does not represent the variance.
*/
JS引擎可以对这个例子应用很多动态优化:
用整数执行所有计算,只在最后调用 Math.sqrt.
时转换为双精度
内联调用
len
函数。将计算提升到循环之外,因为它总是计算相同的东西。
认识到循环是空的并完全消除它。
认识到测试函数永远不会返回结果,因此删除整个测试函数。
即使您将每次调用的结果相加,除 (4) 之外的所有内容都适用。对于 (5),无论哪种方式,最终结果都是一个空函数。
使用 Wasm 引擎无法完成这些步骤中的大部分,因为它无法跨语言边界内联(至少现在没有引擎这样做,AFAICT)。此外,对于 Wasm,假定生产(离线)编译器已经执行了相关的优化,因此 Wasm JIT 往往不如 JavaScript 那样激进,后者不可能进行静态优化。
Andreas 描述了 JavaScript 实现
- 这是一个经典的 'micro benchmark',即您正在测试的代码非常小,测试循环中的其他开销是一个重要因素。例如,从 JavaScript 调用 WebAssembly 会产生开销,这将影响您的结果。你想测量什么?原始处理速度?还是语言边界的开销?
- 由于测试代码的微小变化,您的结果差异很大,从 x300 到 x2。同样,这是一个微基准问题。其他人在使用这种方法衡量性能时也看到了同样的情况,例如this post claims wasm is x84 faster,这显然是错误的!
- 当前的 WebAssembly VM 非常新,而且是 MVP。它会变得更快。您的 JavaScript 虚拟机用了 20 年才达到目前的速度。 JS <=> wasm 边界的性能是 worked on and optimised right now.
要获得更明确的答案,请参阅 WebAssembly 团队的联合论文,其中概述了预期的 runtime performance gain of around 30%
最后,回答你的问题:
Whats the point of WebAssembly if it does not optimise
我认为您对 WebAssembly 将为您做什么有误解。根据上面的论文,运行时性能优化非常适度。但是,仍然有一些性能优势:
- 其紧凑的二进制格式意味着低级性质意味着浏览器可以比 JavaScript 更快地加载、解析和编译代码。预计 WebAssembly 的编译速度会比浏览器下载它的速度快。
- WebAssembly 具有可预测的运行时性能。随着 JavaScript 的进一步优化,性能通常会随着每次迭代而提高。由于 se 优化,它也可以减少。
还有一些与性能无关的优点。
如需更真实的性能衡量,请查看:
- 是use within Figma
- 与 PDFKit 一起使用的结果
两者都是实用的生产代码库。
认真回答
好像
- WebAssembly is far from a ready technology.
实际上确实在这方面发挥了作用,以及在 Firefox 中从 JS 调用 WASM 的性能 was improved in late 2018。 运行 当前 FF/Chromium 中的基准测试产生的结果类似于“从 JS 调用 WASM 实现比从 JS 调用 JS 实现慢 4-10 倍”。尽管如此,引擎似乎并没有跨 WASM/JS 边界内联,并且必须调用与不必调用的开销是显着的(正如其他答案已经指出的那样)。
嘲讽回答
你的基准都是错误的。事实证明,JS 实际上比 WASM 慢了 8-40 倍(FF,Chrome)。 WTF,JS 太懒了。
我打算证明这一点吗?当然(不是)。
首先,我在 C:
中重新实现了您的基准测试代码#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static double lengthC(double x, double y, double x1, double y1) {
double nx = x1 - x;
double ny = y1 - y;
return sqrt(nx * nx + ny * ny);
}
double lengthArrayC(double* a, size_t length) {
double c = 0;
for (size_t i = 0; i < length; i++) {
double a1 = a[i + 0];
double a2 = a[i + 1];
double a3 = a[i + 2];
double a4 = a[i + 3];
c += lengthC(a1,a2,a3,a4);
c += lengthC(a2,a3,a4,a1);
c += lengthC(a3,a4,a1,a2);
c += lengthC(a4,a1,a2,a3);
}
return c;
}
#ifdef __wasm__
__attribute__((import_module("js"), import_name("len")))
double lengthJS(double x, double y, double x1, double y1);
double lengthArrayJS(double* a, size_t length) {
double c = 0;
for (size_t i = 0; i < length; i++) {
double a1 = a[i + 0];
double a2 = a[i + 1];
double a3 = a[i + 2];
double a4 = a[i + 3];
c += lengthJS(a1,a2,a3,a4);
c += lengthJS(a2,a3,a4,a1);
c += lengthJS(a3,a4,a1,a2);
c += lengthJS(a4,a1,a2,a3);
}
return c;
}
__attribute__((import_module("bench"), import_name("now")))
double now();
__attribute__((import_module("bench"), import_name("result")))
void printtime(int benchidx, double ns);
#else
void printtime(int benchidx, double ns) {
if (benchidx == 1) {
printf("C: %f ns\n", ns);
} else if (benchidx == 0) {
printf("avoid the optimizer: %f\n", ns);
} else {
fprintf(stderr, "Unknown benchmark: %d", benchidx);
exit(-1);
}
}
double now() {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
} else {
return sqrt(-1);
}
}
#endif
#define iters 1000000
double a[iters+3];
int main() {
int bigCount = 0;
srand(now());
for (size_t i = 0; i < iters + 3; i++)
a[i] = (double)rand()/RAND_MAX*2e5-1e5;
for (int i = 0; i < 10; i++) {
double startTime, endTime;
double c;
startTime = now();
c = lengthArrayC(a, iters);
endTime = now();
bigCount = (bigCount + (int64_t)c) % 1000;
printtime(1, (endTime - startTime) * 1e9 / iters / 4);
#ifdef __wasm__
startTime = now();
c = lengthArrayJS(a, iters);
endTime = now();
bigCount = (bigCount + (int64_t)c) % 1000;
printtime(2, (endTime - startTime) * 1e9 / iters / 4);
#endif
}
printtime(0, bigCount);
return 0;
}
用 clang 12.0.1 编译:
clang -O3 -target wasm32-wasi --sysroot /opt/wasi-sdk/wasi-sysroot/ foo2.c -o foo2.wasm
并通过导入从 JS 为其提供长度函数:
"use strict";
(async (wasm) => {
const wasmbytes = new Uint8Array(wasm.length);
for (var i in wasm)
wasmbytes[i] = wasm.charCodeAt(i);
(await WebAssembly.instantiate(wasmbytes, {
js: {
len: function (x,y,x1,y1) {
var nx = x1 - x;
var ny = y1 - y;
return Math.sqrt(nx * nx + ny * ny);
}
},
bench: {
now: () => window.performance.now() / 1e3,
result: (bench, ns) => {
let name;
if (bench == 1) { name = "C" }
else if (bench == 2) { name = "JS" }
else if (bench == 0) { console.log("Optimizer confuser: " + ns); /*not really necessary*/; return; }
else { throw "unknown bench"; }
console.log(name + ": " + ns + " ns");
},
},
})).instance.exports._start();
})(atob('AGFzbQEAAAABFQRgBHx8fHwBfGAAAXxgAn98AGAAAAIlAwJqcwNsZW4AAAViZW5jaANub3cAAQViZW5jaAZyZXN1bHQAAgMCAQMFAwEAfAcTAgZtZW1vcnkCAAZfc3RhcnQAAwr2BAHzBAMIfAJ/An5BmKzoAwJ/EAEiA0QAAAAAAADwQWMgA0QAAAAAAAAAAGZxBEAgA6sMAQtBAAtBAWutNwMAQejbl3whCANAQZis6ANBmKzoAykDAEKt/tXk1IX9qNgAfkIBfCIKNwMAIAhBmKzoA2ogCkIhiKe3RAAAwP///99Bo0QAAAAAAGoIQaJEAAAAAABq+MCgOQMAIAhBCGoiCA0ACwNAEAEhBkGQCCsDACEBQYgIKwMAIQRBgAgrAwAhAEQAAAAAAAAAACECQRghCANAIAQhAyABIgQgAKEiASABoiIHIAMgCEGACGorAwAiAaEiBSAFoiIFoJ8gACAEoSIAIACiIgAgBaCfIAAgASADoSIAIACiIgCgnyACIAcgAKCfoKCgoCECIAMhACAIQQhqIghBmKToA0cNAAtBARABIAahRAAAAABlzc1BokQAAAAAgIQuQaNEAAAAAAAA0D+iEAICfiACmUQAAAAAAADgQ2MEQCACsAwBC0KAgICAgICAgIB/CyALfEQAAAAAAAAAACECQYDcl3whCBABIQMDQCACIAhBgKzoA2orAwAiBSAIQYis6ANqKwMAIgEgCEGQrOgDaisDACIAIAhBmKzoA2orAwAiBBAAoCABIAAgBCAFEACgIAAgBCAFIAEQAKAgBCAFIAEgABAAoCECIAhBCGoiCA0AC0ECEAEgA6FEAAAAAGXNzUGiRAAAAACAhC5Bo0QAAAAAAADQP6IQAkLoB4EhCgJ+IAKZRAAAAAAAAOBDYwRAIAKwDAELQoCAgICAgICAgH8LIAp8QugHgSELIAlBAWoiCUEKRw0AC0EAIAuntxACCwB2CXByb2R1Y2VycwEMcHJvY2Vzc2VkLWJ5AQVjbGFuZ1YxMS4wLjAgKGh0dHBzOi8vZ2l0aHViLmNvbS9sbHZtL2xsdm0tcHJvamVjdCAxNzYyNDliZDY3MzJhODA0NGQ0NTcwOTJlZDkzMjc2ODcyNGE2ZjA2KQ=='))
现在,毫不奇怪,从 WASM 调用 JS 函数比从 WASM 调用 WASM 函数慢很多。 (实际上,WASM→WASM 并没有调用。你可以看到 f64.sqrt
被内联到 _start
。)
(最后一个有趣的数据点是 WASM→WASM 和 JS→JS 似乎具有相同的成本(在我的 E3-1280 上每个内联 length(…)
大约 1.5 ns)。免责声明:完全有可能我的基准比原来的问题更糟糕。)
结论
WASM 并不慢,越界才是。现在和可预见的未来,除非是重要的计算任务,否则不要将东西放入 WASM。 (即便如此,这也取决于。有时候,JS 引擎真的很聪明。有时候。)