为什么webAssembly的功能比同样的JS函数快300倍

Bli*_*n67 17 javascript performance webassembly

找到300*行的长度

首先,我已经阅读了为什么我的WebAssembly功能比JavaScript等效的慢?

但它对这个问题没有什么启示,而且我投入了大量的时间,很可能是那些黄色的东西.

我不使用全局变量,我不使用任何内存.我有两个简单的函数,可以找到一个线段的长度,并将它们与普通的旧Javascript中的相同内容进行比较.我有4个参数3个本地人并返回一个浮点数或双倍.

在Chrome上,Javascript比webAssembly快40倍,而在Firefox上,wasm 比Javascript慢近300倍.

jsPref测试用例.

我在jsPref WebAssembly V Javascript数学中添加了一个测试用例

我究竟做错了什么?

  1. 我错过了一个明显的错误,不好的做法,或者我正在遭受编码器的愚蠢.
  2. WebAssembly不适用于32位操作系统(赢得10台笔记本电脑i7CPU)
  3. WebAssembly远非现成的技术.

请选择1.

我已经阅读了webAssembly用例

通过定位WebAssembly重用现有代码,嵌入在更大的JavaScript/HTML应用程序中.这可以是简单的帮助程序库,也可以是面向计算的任务卸载.

我希望我可以用webAssembly替换一些几何库来获得一些额外的性能.我希望它会很棒,比10倍或更快.但是WTF要慢300倍.


UPADTE

这不是JS优化问题.

为了确保优化具有尽可能小的效果,我使用以下方法测试以减少或消除任何优化偏差.

  • 计数器c += length(... 以确保执行所有代码.
  • bigCount += c确保执行整个功能.不需要
  • 每个功能4行,以减少内联歪斜.不需要
  • 所有值都是随机生成的双精度数
  • 每个函数调用返回不同的结果.
  • 在JS中添加较慢的长度计算Math.hypot用于证明代码正在运行.
  • 添加了空调用,返回第一个参数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",
    }
],
Run Code Online (Sandbox Code Playgroud)

更新结果.

因为测试中有更多的开销,结果更接近但JS代码仍然快两个数量级.

注意函数Math.hypot 有多慢.如果优化有效,那么函数将接近更快的len函数.

  • WebAssembly13389μs
  • Javascript728μ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.

*/
Run Code Online (Sandbox Code Playgroud)

如果它没有优化,那么WebAssmbly的重点是什么

更新结束


所有与问题有关的东西.

找到一条线的长度.

自定义语言的原始来源

   
// 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;
}
Run Code Online (Sandbox Code Playgroud)

代码编译为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
)
)
Run Code Online (Sandbox Code Playgroud)

以十六进制字符串编译的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);
    }
Run Code Online (Sandbox Code Playgroud)

并且测试代码对于所有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",
    }
]
Run Code Online (Sandbox Code Playgroud)

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.
*/
Run Code Online (Sandbox Code Playgroud)

Col*_*inE 7

Andreas描述了为什么JavaScript实现最初被观察到x300更快的一些很好的理由.但是,您的代码还存在许多其他问题.

  1. 这是一个经典的"微基准测试",即您测试的代码非常小,测试循环中的其他开销是一个重要因素.例如,从JavaScript调用WebAssembly会产生开销,这会影响您的结果.你想测量什么?原始处理速度?还是语言边界的开销?
  2. 由于测试代码的细微变化,您的结果差别很大,从x300到x2.同样,这是一个微观基准问题.其他人在使用这种方法测量性能时也看到了相同的情况,例如这篇帖子声称自己的x84更快,这显然是错误的!
  3. 当前的WebAssembly VM非常新,而且是MVP.它会变得更快.您的JavaScript VM已有20年的时间才能达到目前的速度.JS <=> wasm边界的性能正在进行中并正在进行优化.

有关更明确的答案,请参阅WebAssembly团队的联合文件,其中概述了预期的运行时性能增益约为30%

最后,回答你的观点:

如果没有优化,那么WebAssembly的重点是什么

我认为你对WebAssembly会为你做什么有误解.基于上面的论文,运行时性能优化非常适度.但是,仍然有许多性能优势:

  1. 其紧凑的二进制格式意味着低级别的性质意味着浏览器可以比JavaScript更快地加载,解析和编译代码.预计WebAssembly的编译速度可能比浏览器下载速度快.
  2. WebAssembly具有可预测的运行时性能.使用JavaScript,性能通常随着每次迭代而增加,因为它进一步优化.它也可以由于se优化而减少.

还有许多与性能无关的优点.

要获得更真实的性能测量,请查看:

两者都是实用的生产代码库.


And*_*erg 5

JS 引擎可以对这个例子应用很多动态优化:

  1. 使用整数执行所有计算,并且仅在最终调用 Math.sqrt 时转换为双精度值。

  2. 内联对len函数的调用。

  3. 将计算提升到循环之外,因为它总是计算相同的东西。

  4. 认识到循环是空的并完全消除它。

  5. 认识到结果永远不会从测试函数返回,因此删除测试函数的整个主体。

即使您添加每次调用的结果,除 (4) 之外的所有内容也适用。对于(5),无论哪种方式,最终结果都是一个空函数。

使用 Wasm 引擎无法完成这些步骤中的大部分,因为它无法跨语言边界内联(至少今天没有引擎这样做,AFAICT)。此外,对于 Wasm 来说,假设生产(离线)编译器已经执行了相关的优化,因此 Wasm JIT 往往不如 JavaScript 的激进,因为 JavaScript 是不可能进行静态优化的。

  • 我想说,这是从单个纳米基准中得出的相当大胆的结论。 (2认同)