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

时间:2018-01-09 17:49:10

标签: javascript performance webassembly

查找300 *行的长度

我首先阅读了Why is my WebAssembly function slower than the JavaScript equivalent?

的答案

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

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

在Chrome上,Javascript比webAssembly快40倍,而在Firefox上,ism几乎比Javascript慢<300> 。

jsPref测试用例。

我已将测试用例添加到jsPref WebAssembly V Javascript math

我做错了什么?

无论

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

    我已阅读webAssembly use case

      

    通过定位嵌入较大版本的WebAssembly来重用现有代码   JavaScript / HTML应用程序。这可能是简单的任何事情   辅助库,面向计算任务卸载。

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

    UPADTE

    这不是JS优化问题。

    为确保优化尽可能少,我已使用以下方法测试,以减少或消除任何优化偏差。

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

    • 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.
    
    */

    如果没有优化,那么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;
    }

    代码编译为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.
    */

2 个答案:

答案 0 :(得分:6)

Andreas描述了JavaScript实现为initially observed to be x300 faster的原因。但是,您的代码还存在许多其他问题。

  1. 这是一个经典的微基准测试,即您正在测试的代码非常小,测试循环中的其他开销是一个重要因素。例如,从JavaScript调用WebAssembly会产生开销,这会影响您的结果。你想测量什么?原始处理速度?还是语言边界的开销?
  2. 由于测试代码的细微变化,您的结果差异很大,从x300到x2。同样,这是一个微观基准问题。其他人在使用这种方法衡量绩效时也看到了相同的情况,例如this post claims wasm is x84 faster,这显然是错误的!
  3. 当前的WebAssembly VM非常新,而且是MVP。它会变得更快。您的JavaScript VM已有20年的时间才能达到目前的速度。 JS&lt; =&gt;的性能。 wasm boundary is worked on and optimised right now
  4. 有关更明确的答案,请参阅WebAssembly小组的联合文件,其中概述了预期的runtime performance gain of around 30%

    最后,回答你的观点:

      

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

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

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

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

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

答案 1 :(得分:4)

JS引擎可以对此示例应用大量动态优化:

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

  2. 内联对len功能的调用。

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

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

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

  6. 即使添加每个通话的结果,除了(4)之外的所有通知都适用。使用(5)最终结果是一个空函数。

    使用Wasm,引擎无法完成大部分步骤,因为它无法跨越语言边界(至少今天没有引擎可以做到这一点,AFAICT)。此外,对于Wasm,假设生产(离线)编译器已经执行了相关的优化,因此对于JavaScript来说,Wasm JIT的攻击性往往低于JavaScript,而静态优化是不可能的。