草庐IT

javascript - 为什么 webAssembly 函数比相同的 JS 函数慢近 300 倍

coder 2024-05-06 原文

查找第 300 行的长度*慢
首先,我已阅读 Why is my WebAssembly function slower than the JavaScript equivalent? 的答案
但这并没有说明问题,而且我已经投入了大量时间,这很可能是靠墙的黄色东西。
我不使用全局变量,我不使用任何内存。我有两个简单的函数,它们可以找到线段的长度,并将它们与普通旧 Javascript 中的相同内容进行比较。我有 4 个参数 3 个本地人,并返回一个浮点数或 double 数。
在 Chrome 上,Javascript 比 webAssembly 快 40 倍,在 Firefox 上,wasm 几乎比 Javascript 慢 300 倍。
jsPref 测试用例。
我在 jsPref WebAssembly V Javascript math 中添加了一个测试用例
我究竟做错了什么?
任何一个

  • 我错过了一个明显的错误、不好的做法,或者我正在遭受编码人员的愚蠢。
  • WebAssembly 不适用于 32 位操作系统(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 行以减少内联偏差。不需要
  • 所有值都是随机生成的 double 数
  • 每个函数调用返回不同的结果。
  • 在 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",
        }
    ],

    更新的结果。
    因为测试中有更多的开销,所以结果更接近,但 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",
        }
    ]

    火狐上的测试结果是

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

    最佳答案

    Andreas 描述了 JavaScript 实现是 initially observed to be x300 faster 的一些充分理由。 .但是,您的代码还有许多其他问题。

  • 这是一个经典的“微基准测试”,即您正在测试的代码非常小,以至于您的测试循环中的其他开销是一个重要因素。例如,从 JavaScript 调用 WebAssembly 会产生开销,这将影响您的结果。你想测量什么?原始处理速度?还是语言边界的开销?
  • 由于测试代码的微小变化,您的结果差异很大,从 x300 到 x2。同样,这是一个微观基准问题。其他人在使用这种方法来衡量性能时也看到了同样的情况,例如 this post claims wasm is x84 faster ,这显然是错误的!
  • 当前的 WebAssembly VM 非常新,并且是 MVP。它会变得更快。您的 JavaScript VM 已经用了 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 一起使用的结果

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

    关于javascript - 为什么 webAssembly 函数比相同的 JS 函数慢近 300 倍,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48173979/

    有关javascript - 为什么 webAssembly 函数比相同的 JS 函数慢近 300 倍的更多相关文章

    1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    6. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

      它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

    7. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

      我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

    8. ruby - Infinity 和 NaN 的类型是什么? - 2

      我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

    9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    10. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

    随机推荐