WebAssembly:解决Web上的性能问题

    卡米洛·雷耶斯
    分享

    在现代JavaScript中,目标通常是找到所有优化浏览器性能的方法。有些时候,web应用程序需要高性能,并期望浏览器能够跟上。

    由于引擎处理语言的方式,传统JavaScript有性能限制。作为页面的一部分呈现的解释性(甚至是jit编译)语言只能得到这么多——即使从最强大的硬件上也是如此。

    WebAssembly完全是为了解决性能问题而设计的。它可以克服传统JavaScript无法解决的瓶颈问题。在WebAssembly中,不需要解析和解释代码。WebAssembly充分利用其字节码格式,为您提供与本机程序相匹配的运行时速度。

    从另一种角度考虑:将传统JavaScript想象成一种好的、万能的工具,可以让您到达任何地方。相反,WebAssembly是一种高性能的解决方案,能够达到接近本机的速度。这是两个独立的编程工具。

    我的问题是:WebAssembly会取代传统的JavaScript吗?如果不是,是否值得投资学习WebAssembly?

    什么是WebAssembly?

    WebAssembly是一种可以发送到浏览器的不同类型的代码。它在字节码,这意味着它在到达浏览器之前已经以低级汇编语言发布。字节码不是手工编写的,但可以从任何编程语言(如c++或Rust)编译。然后浏览器可以获取任何WebAssembly代码,将其作为本机代码加载,从而实现高性能。

    您可以将此WebAssembly字节码视为一个模块:浏览器可以获取、加载并执行该模块。每个WebAssembly模块都具有导入和导出功能,其行为与JavaScript对象非常相似。WebAssembly模块的工作原理与任何其他JavaScript代码非常相似,只是它以接近本机的速度运行。从程序员的角度来看,您可以使用与当前JavaScript对象相同的方式来使用WebAssembly模块。这意味着你对JavaScript和web的了解也可以转移到WebAssembly编程中。

    WebAssembly工具通常由c++编译器组成。在当前的开发中有许多工具,但是已经成熟的工具是Emscripten.该工具将c++代码编译成WebAssembly模块,并构建可以在任何地方运行的符合标准的模块。编译后的输出将有一个WASM文件扩展名,以表明它是一个WebAssembly模块。

    WebAssembly的一个优点是,当你获取模块时,你有所有相同的HTTP缓存头。另外,您可以使用IndexedDB缓存WASM模块,也可以使用会话存储.缓存策略围绕着缓存获取API请求,并通过保留本地副本来避免另一个请求。因为WebAssembly模块是字节码格式的,所以你可以把这个模块当作一个字节数组并存储在本地。

    既然我们知道了WebAssembly是什么,那么它的局限性是什么呢?

    已知的限制

    JavaScript运行在不同于任何典型c++程序的环境中。因此,限制包括本机api在浏览器环境中可以做什么。

    网络函数必须是异步和非阻塞操作。所有底层JavaScript网络功能在浏览器的Web API中都是异步的。然而,WebAssembly并不能从异步I/ o绑定操作中获益。I/O操作必须等待网络响应,这使得所有接近本机的性能增益可以忽略不计。

    在浏览器中运行的代码,在沙箱环境中运行,并且不能访问文件系统。您可以创建内存中的虚拟文件系统,而不是预先加载数据。

    应用程序的主循环使用合作多任务处理,每个事件都轮流执行。web上的事件通常来自鼠标点击、手指点击或拖放操作。事件必须将控制权返回给浏览器,以便处理其他事件。避免劫持主事件循环是明智的,因为这可能变成调试的噩梦。DOM事件通常绑定到UI更新,这是昂贵的。这给我们带来了另一个限制。

    WebAssembly不能访问DOM;它依赖于JavaScript函数来进行任何更改。目前,有一项提案允许与web上的DOM对象的互操作性.仔细想想,DOM重绘既缓慢又昂贵。从近乎本地的性能中获得的所有好处都被DOM破坏了。一种解决方案是将DOM抽象为内存中的本地副本,稍后可以通过JavaScript进行协调。

    在WebAssembly中,一些好的建议是坚持执行速度非常快的东西。在工作中使用能产生最大性能收益的工具,同时避免陷阱。可以把WebAssembly看作是一个超高速的系统,它可以在没有任何阻塞的情况下独立运行。

    WebAssembly中的浏览器兼容性很差,除了现代浏览器。IE中没有支持。然而,Edge 16+支持WebAssembly。所有现代的大型播放器,如Firefox 52+, Safari 11+和Chrome 57+都支持WebAssembly。一个想法是在WebAssembly模块和JavaScript之间进行特性检测和特性对等。这样你就不会破坏网页,现代浏览器从WebAssembly中获得所有的性能提升。

    我可以用wasm吗?主要浏览器对wasm特性的支持数据来自caniuse.com。

    WebAssembly演示

    足够的讨论;是时候做一组不错的演示了。这次我们将探索WebAssembly中的导出和导入功能。导出和导入功能是WebAssembly互操作性的标志。这些函数使程序员能够像使用其他JavaScript对象一样使用WebAssembly模块。

    一个导出功能是你从WebAssembly模块中获得的。一旦模块加载,您将在其中找到导出函数instance.exports.对于这个演示,我将导出一个添加函数,它计算作为参数传入的两个数字的和。计算将在接近本地的WebAssembly代码中执行。在这个演示中,导出函数将是一个纯JavaScript函数——这意味着它是无状态且不可变的。

    一个导入功能是你提供给WebAssembly模块的一个。它是一个简单的JavaScript对象,有一个回调函数。然后,模块用WebAssembly中的参数调用函数。我将导入一个简单的回调,它从WebAssembly接收一个参数。参数是一个赋值为42的常量。然后我将使用这个值从JavaScript中设置DOM:

    <p<跨度class="token punctuation">>添加结果:<跨度class="token tag"><跨度<跨度class="token attr-name">id<跨度class="token attr-value"><跨度class="token punctuation">"addResult<跨度class="token punctuation">"<跨度class="token punctuation">><跨度class="token tag">跨度<跨度class="token punctuation">><跨度class="token tag">p<跨度class="token punctuation">><跨度class="token tag"><p<跨度class="token punctuation">>简单的结果:<跨度class="token tag"><跨度<跨度class="token attr-name">id<跨度class="token attr-value"><跨度class="token punctuation">"simpleResult<跨度class="token punctuation">"<跨度class="token punctuation">><跨度class="token tag">跨度<跨度class="token punctuation">><跨度class="token tag">p<跨度class="token punctuation">>

    导出WebAssembly函数

    首先,让我们看一下WebAssembly模块的文本格式。这是WASM模块的文本表示形式,可以由人类读取。它是为文本编辑器或任何其他可以使用纯文本的工具设计的:

    (module (func $add (param $lhs i32) (param $rhs i32) (result i32) get_local $lhs get_local $rhs i32) (export "add" (func $add)))

    理解这里的每个细节并不太重要。这是WebAssembly模块的文本格式,您经常可以在WAT文件扩展名中找到它。的i32.add使用近本地代码执行添加。的出口“添加”然后抓住函数添加美元并使它对JavaScript可用。

    要加载WebAssembly模块,你可以这样做:

    WASM模块的URL<跨度class="token keyword">常量<跨度class="token constant">WASM_ADD_MODULE<跨度class="token operator">=<跨度class="token string">“https://myhost.com/add.wasm”<跨度class="token punctuation">;<跨度class="token function">获取<跨度class="token punctuation">(<跨度class="token constant">WASM_ADD_MODULE<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">响应<跨度class="token arrow operator">= >响应<跨度class="token punctuation">.<跨度class="token method function property-access">arrayBuffer<跨度class="token punctuation">(<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">字节<跨度class="token arrow operator">= ><跨度class="token known-class-name class-name">WebAssembly<跨度class="token punctuation">.<跨度class="token method function property-access">实例化<跨度class="token punctuation">(字节<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">结果<跨度class="token arrow operator">= ><跨度class="token dom variable">文档<跨度class="token punctuation">.<跨度class="token method function property-access">getElementById<跨度class="token punctuation">(<跨度class="token string">“addResult”<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token property-access">innerHTML<跨度class="token operator">=结果<跨度class="token punctuation">.<跨度class="token property-access">实例<跨度class="token punctuation">.<跨度class="token property-access">出口<跨度class="token punctuation">.<跨度class="token method function property-access">添加<跨度class="token punctuation">(<跨度class="token number">1<跨度class="token punctuation">,<跨度class="token number">5<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">;

    Fetch API从URL获取模块,并将其转换为字节数组。这个字节数组来自response.arrayBuffer.注意,检查导出的函数exports.add表示它被编译为本机代码:

    函数<跨度class="token number">0<跨度class="token punctuation">(<跨度class="token punctuation">)<跨度class="token punctuation">{<跨度class="token punctuation">[本机代码<跨度class="token punctuation">]<跨度class="token punctuation">}

    一个问题是使用WebAssembly.instantiate是比宽大吗WebAssembly.instantiateStreaming.后者表示WASM模块必须具有MIME类型应用程序/ wasm.你会遇到这个问题,当你得到TypeError和它一起工作的时候。如果您通过CDN提供WASM模块,并且不能控制MIME类型,那么使用WebAssembly.instantiateWebAssembly.instantiateStreaming比前者更高效,但它是一种较新的web API,所以还不能在所有现代浏览器中使用。

    导入WebAssembly函数

    对于导入的函数,请以文本格式从此模块开始。想象一下在WebAssembly中进行这个cpu受限且代价高昂的计算。如此强烈,事实上,这是生命和一切终极问题的答案。

    例如:

    (module (func $i (import "imports" " importted_func ") (param i32)) (func (export "exported_func") i32.)Const 42调用$i))

    注意这个常数手机等。const 42被宣布。然后,使用导入的函数调用回调函数打电话给我美元.的出口“exported_func”声明从JavaScript调用的导出函数的名称。

    在JavaScript中,我们可以这样处理这个模块:

    常量<跨度class="token constant">WASM_SIMPLE_MODULE<跨度class="token operator">=<跨度class="token string">“https://myhost.com/simple.wasm”<跨度class="token punctuation">;<跨度class="token keyword">常量<跨度class="token function-variable function">simpleFn<跨度class="token operator">=<跨度class="token punctuation">(<跨度class="token parameter">参数<跨度class="token punctuation">)<跨度class="token arrow operator">= ><跨度class="token dom variable">文档<跨度class="token punctuation">.<跨度class="token method function property-access">getElementById<跨度class="token punctuation">(<跨度class="token string">“simpleResult”<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token property-access">innerHTML<跨度class="token operator">=参数<跨度class="token punctuation">;<跨度class="token keyword">常量importSimpleObj<跨度class="token operator">=<跨度class="token punctuation">{进口<跨度class="token operator">:<跨度class="token punctuation">{imported_func<跨度class="token operator">:simpleFn<跨度class="token punctuation">}<跨度class="token punctuation">}<跨度class="token punctuation">;<跨度class="token function">获取<跨度class="token punctuation">(<跨度class="token constant">WASM_SIMPLE_MODULE<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">响应<跨度class="token arrow operator">= >响应<跨度class="token punctuation">.<跨度class="token method function property-access">arrayBuffer<跨度class="token punctuation">(<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">字节<跨度class="token arrow operator">= ><跨度class="token known-class-name class-name">WebAssembly<跨度class="token punctuation">.<跨度class="token method function property-access">实例化<跨度class="token punctuation">(字节<跨度class="token punctuation">,importSimpleObj<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">.<跨度class="token method function property-access">然后<跨度class="token punctuation">(<跨度class="token parameter">结果<跨度class="token arrow operator">= >结果<跨度class="token punctuation">.<跨度class="token property-access">实例<跨度class="token punctuation">.<跨度class="token property-access">出口<跨度class="token punctuation">.<跨度class="token method function property-access">exported_func<跨度class="token punctuation">(<跨度class="token punctuation">)<跨度class="token punctuation">)<跨度class="token punctuation">;

    看看importSimpleObj,因为这是具有回调函数的JavaScript对象。的exports.exported_func然后执行WebAssembly模块。一旦调用,导入的函数simpleFn使用常量参数运行。

    下面是一个你可以玩的CodePen演示。请随意检查此代码示例中的每个函数和对象。这将使您对与WebAssembly集成所需的粘合代码有一个良好的感觉。

    看钢笔WebAssembly演示由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)CodePen

    结论

    回答我最初的问题,WebAssembly是对Web的一个很好的补充。它并不是JavaScript的替代品,只是增强了当前的web技术。任何追求速度、效率和高性能的web工程师都应该关注WebAssembly。JavaScript是执行和处理WebAssembly结果的胶水代码。

    一种想法是移植现有的JavaScript代码,这些代码执行大量cpu绑定的工作——比如,DOM的内存虚拟表示,它只抽象真正的DOM。例如,WebAssembly端口也可以为那些还不支持WebAssembly的浏览器提供一个优雅的退步。

    随着WebAssembly模块变得越来越普遍,npm包可能会附带这些模块,这些模块背后是漂亮的JavaScript抽象。这既增强了当前的生态系统,又增加了代码重用。也许有一天,你不再需要编写自己的WebAssembly模块。

    WebAssembly的可能性是无限的。这是一个您现在就可以添加到您的武器库中的工具—用于解决在Web上遇到的许多性能瓶颈。

    快速提示:今天就在浏览器中尝试WebAssembly"></a>
         <div class= 快速提示:今天就在浏览器中尝试WebAssembly 埃利奥•Qoshi
    编程的未来:WebAssembly &后JavaScript时代"></a>
         <div class= 编程的未来:WebAssembly &后JavaScript时代 埃里克•艾略特
    5常见营销问题的Web开发解决方案"></a>
         <div class= 5常见营销问题的Web开发解决方案 约书亚·克劳斯
    必威西盟体育网页登录SitePoint播客第181期:解决的问题比制造的多"></a>
         <div class= 必威西盟体育网页登录SitePoint播客第181期:解决的问题比制造的多 圆锥形石垒广泛
    Baidu