引言
先看下 官网 给的定义。
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
WebAssembly
是基于栈式虚拟机的二进制指令集,可以作为编程语言的编译目标,能够部署在 web
客户端和服务端的应用中。
第一次看到这个定义的时候是一头雾水,翻了一些资料渐渐有了点轮廓,下边分享下我目前的理解。
首先 WebAssembly
是由 Web
和 Assembly
两个词构成,其中 Web
表明它一定和前端有关。Assembly
的意思是汇编,汇编对应机器码,而机器码和 CPU
的指令集有关,接下来补一下相关的知识。
相关概念
其中指令集、操作系统相关的知识,之前总结过几篇文章,到底学哪一门编程语言、x86,x64,x86-64,amd64,arm指令集架构之间的关系、linux和Android的关系,可以先过去看一下,这里的话抽主要的部分回顾一下。
参考上图,计算机的主要架构如上。最底层是 CPU
的指令集,主要分为复杂指令集和简单指令集。
复杂指令集是 x86
、x64(也叫 x86-64, amd64)
两种架构,专利在 Intel
和 AMD
两家公司手里, 该架构 CPU
主要是 Intel
和 AMD
两家公司,这种 CPU
常用在 PC
机上,包括 Windows
,macOS
和 Linux
。
简单指令集是 arm
一种架构,专利在 ARM
公司手里,该架构 CPU
主要有高通、三星、苹果、华为海思、联发科等公司。这种 CPU
常用在手机上,包括安卓和苹果。
指令集是什么呢?直接把阮一峰的老师的一个 例子 粘过来,大家可以看一下。
c
语言的源程序。
1 | int add_a_and_b(int a, int b) { |
所对应的汇编就是下边的样子。
1 | _add_a_and_b: |
这里的 push
、mov
每一条指令就是指令集规定的内容,规定了操作码、操作数以及具体的功能。当然这里是用汇编表示的,主要是为了我们人类来读写,最终还会转成 0,1
序列。上边每个单词都会有一个数字相对应,比如 add
指令对应 00000011
。
通过规定的指令集(加法的指令,压栈指令等),编写相关程序,然后 CPU
就会一条一条的执行,最终实现相应的功能。
而 WebAssembly
就规定了一套指令集,更准确的来说是虚拟指令集,因为这套指令集是跑在虚拟机上的,而不是直接由硬件运行。
历史
上边我们知道了 WebAssembly
的 Assembly
,即汇编,也就是指令集。下边在回顾下 Web
,即 WebAssembly
诞生的原因。
这里就得谈到 javaScript
了,众所周知, javaScript
是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于我们开发者,确实很方便,但对于运行它的引擎就很有问题了。参考 这里 的一张图,看一下 V8
引擎从 js
源码到执行的一个过程。
由于 js
的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以 V8
引擎采用了 JIT
(即时编译技术) 技术,监控一些经常执行的代码,将其编译成 CPU
直接执行的机器码,提高执行速度。但由于 js
动态类型,在某些情况下还得反优化,回到字节码进行执行。
随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。
NaCl/PNaCl
2011
年 Google
在 Chrome
中使用了 NaCl
技术,可以使得 C
语言编写的程序运行到浏览器中,下边是维基百科 的定义。
Google Native Client(缩写为NaCl),是一个由谷歌所发起的开放源代码计划,采用BSD许可证。它采用沙盒)技术,让Intel x86、ARM或MIPS子集的机器代码直接在沙盒上运行。它能够从浏览器直接运行程序机器代码,独立于用户的操作系统之外,使Web应用程序可以用接近于机器代码运作的速度来运行,同时兼顾安全性。其功能类似于微软的 ActiveX,但是ActiveX只支持视窗系统。
但一个完整的 NaCl
应用,在分发时需要提供支持多个架构平台(X86 / X64 / ARM 等)的模块文件,后来谷歌又推出了与底层架构无关的 PNaCl
技术。但由于其开发难度、兼容性等问题最终没有普及开来。在 2017
年 Google
宣布放弃 PNaCl
转向 WebAssembly
。
ASM.js
ASM.js
是 Mozilla
在 2013
年推出的,是 javaScript
的一个严格子集,可以作为 C/C++
编译的目标语言,从而使得 js
引擎可以采用 AOT(Ahead Of Time)
的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。
ASM.js
通常不直接编写,而是作为一种通过编译器生成的中间语言,该编译器获取 C++
或其他语言的源代码,然后输出 ASM.js
。
例如下边的 C
语言代码。
1 | int f(int i) { |
经过编译器编译会生成下边的 js
代码。
1 | function f(i) { |
注意这里的|0
在 js
中相当于和 0
进行了或操作,所以不影响原本的逻辑。在 asm.js
中起到了类型标记的作用,这样 js
引擎执行的时候就知道 i
是一个整型,返回值是一个整型。除了或操作这种,ASM.js
标准中还规定了很多类似的标记规则,用于告诉 js
引擎变量的类型,便于进行 AOT
优化。
这看起来和 TypeScript
很像,但其实不是一种东西。TypeScript
是 js
的一个超集,浏览器并不能直接执行 ts
,还需要转换为 js
去执行。ts
主要是帮助我们开发人员去看的,增加了代码的可读性,也可以让编辑器提前发现一些错误。而 asm.js
是用于引擎的编译优化。
WebAssembly
接下来看一下 WebAssembly
的历史。
2015 年 4 月,WebAssembly Community Group 成立;
2015 年 6 月,WebAssembly 第一次以 WCG 的官方名义向外界公布;
2016 年 8 月,WebAssembly 开始进入了漫长的 “Browser Preview” 阶段;
2017 年 2 月,WebAssembly 官方 LOGO 在 Github 上的众多讨论中被最终确定;同年同月,一个历史性的阶段,四大浏览器(FireFox、Chrome、Edge、WebKit)在 WebAssembly 的 MVP(最小可用版本)标准实现上达成共识,这意味着 WebAssembly 在其 MVP 标准上的 “Brower Preview” 阶段已经结束;
2017 年 8 月,W3C WebAssembly Working Group 成立,意味着 WebAssembly 正式成为 W3C 众多技术标准中的一员。
WebAssembly
于 2019
年 12
月 5
日成为万维网联盟(W3C
)的推荐标准,与 HTML
,CSS
和 JavaScript
一起成为 Web
的第四种语言。
可以看一下目前浏览器的支持程度,已经算比较高了。
初体验
内部结构
目前已经有了将 C/C++
、Rust
、ts
、C#
、Go
、Kotlin
、Swift
等语言转换为 WebAssembly(wasm)
的工具,下边我们体验一下 C++
转换的过程。
首先编写一个 C++
程序 fibonacci.cc
,斐波纳契数字的递归写法。
1 |
|
函数的定义置在 extern “C” {}
结构中,是为了防止函数名编译后被改变。EMSCRIPTEN_KEEPALIVE
是为了确保函数不会在编译器的编译过程中,被 DCE(Dead Code 」limination)
过程处理掉。
然后需要安装 Emscripten
用来将 C++
程序编译为 WebAssembly(wasm)
的程序,安装后执行下边的命令。
1 | emcc fibonacci.cc -s WASM=1 -O3 --no-entry -o fibonacci.wasm |
-s WASM=1
表明编译成 Webassembly
的程序,-O3
表明编译的优化程度,–no-entry
参数告诉编译器没有声明 main
函数,-o
指定生成的文件名。
让我们看一下生成的字节码文件 fibonacci.wasm
。
1 | Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F |
让我们来解读下,最开始的前八个字节 0x0 0x61 0x73 0x6d 0x1 0x0 0x0 0x0
表明当前是一个 wasm
的模块。然后会分很多 Section
,Function Section
, Code Section
等等,都有特定的数字对应,还有就是文章开头讲的指令操作符所对应的一些数字。
看着上边的字节码仿佛回到了上古时期直接用机器码编程的时代,当年出现了汇编语言。这里也会有类似汇编的东西,那就是 WAT(WebAssembly Text Format)
。
需要安装 WABT , 然后执行 wasm2wat
命令。
1 | ../wabt/bin/wasm2wat fibonacci.wasm -o fibonacci.wat |
然后就生成了 fibonacci.wat
文件。
1 | (module |
上边的格式属于 「S- 表达式」, Lisp
语言就是采用的这种表达式,每条语句都是先执行最里边括号的表达式然后依次展开。
使用方法
上边主要介绍了 .wasm
具体长什么样子,下边看一下怎么用到浏览器中。
从 .wasm
源文件到实例化的对象主要有三个步骤,加载 -> 编译 -> 实例化 -> 调用
。
加载:读取 .wasm
字节码到本地中,一般是通过 fetch
从网络中取得。
编译:在 Worker 线程进行,编译成平台相关的代码。
实例化:将宿主环境的一些对象、方法导入到 wasm
模块中,比如导入操作 dom
的方法。
调用:通过上一步已经实例化的对象,来调用 wasm
模块中的方法。
主要有两种类型的 API
,一种是 js
提供的 api
,另一种是 Web
提供的 api
,Web
提供的 api
支持流式编译实例化。
js
的方法,WebAssembly.instantiate(bufferSource, importObject)
,可以完成编译和实例化。
bufferSource
是含有效 Wasm
模块二进制字节码的 ArrayBuffer
或 TypedArray
对象。
importObject
是要导入到 Wasm
模块中的对象。
方法在调用后返回一个Promise
对象,resolve
后返回一个对象,该对象包含编译好的 module
和已经实例化的 instance
,模块导出的方法可以通过 instance
对象进行调用。
web
的方法,WebAssembly.instantiateStreaming(source, importObject)
。
不同之处在于第一个参数,这里的 source
指的是尚未 Resolve
的 Response
对象(window.fetch
调用后会返回该对象),好处就是可以边读取 .wasm
字节流,边进行编译。
其他参数和返回值和 js
的 api
均一致。
js API 尝试
先简单的尝试一下,我们直接构造一个 wasm
模块的 TypedArray
对象,该模块包含了一个 add
方法,然后调用 WebAssembly.instantiate
进行编译和实例化。
对应的 C++
代码。
1 |
|
对应的 .wasm
字节码。
1 | 00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60 |
然后直接在控制台输入下边的代码。
1 | WebAssembly.instantiate(new Uint8Array(` |
然后就会看到输出了 2 + 4 = 6
。
Web API 尝试
我们再尝试一下流式编译。直接使用之前的斐波纳契数字的 fibonacci.wasm
模块。
首先我们需要提供一个简单的 HTTP
服务,用来返回 .wasm
文件。
新建一个 node.js
文件。
1 | const http = require('http'); |
然后来编写我们的 html
文件,讲到斐波那契数字,我们顺便做一个性能的测试,来比较一下使用 wasm
的方式和原生 js
的求解速度。
1 |
|
然后执行 node node.js
开启 http
服务,接着在浏览器中打开 http://localhost:8888/index.html
,控制台中输出如下:
1 | 斐波纳切数字: 5,运行 10 次 |
整理成表格看一下:
可以看到 wasm
很明显的提高了运行速度,运行时间稳定在 js
的一半,当规模达到 45
的时候,wasm
的运行时间比 js
少了整整 8
秒。
这里也可以看出,如果对于计算密集型的应用,wasm
可以大展身手了。
前端应用
来看一些目前已经成功落地的 WebAssembly
的应用。
eBay
的条形码扫描eBay
在原生应用中有专门的C++
库用于条形码的扫描,在H5
中利用开源JavaScript
库BarcodeReader
做了一个带条形码扫描功能的Web版本。 问题是它只有在20%
的时间表现良好。 剩余的80%
的时间运行非常缓慢,准确率也不高。最终的解决方案是通过
wasm
,将原有的c++
库引入,以及业界十分有名的、基于C
语言编写的开源条形码扫描库ZBar
引入,再加上原本的js
库,三者协助,最终识别率达到了100%
。产品上线后的最终效果如下图所示。
产品在上线使用了一段时间后,
eBay
技术团队对应用的条形码扫描情况进行了统计,结果发现有53%
的成功扫描来自于ZBar
;34%
来自于自研的C++
库。剩下的13%
则来自于第三方的JavaScript
库实现。可见,其中通过Wasm
实现得到的扫描结果占据了总成功次数的87%
。更详细的过程可以参考 WebAssembly在eBay的实践:速度提升50倍。
AutoCAD
AutoCAD
是一款由将近40
年历史的知名桌面端设计软件,被广泛地用于土木建筑、装饰装潢、工业制图等多个领域中。最初基于
C++
编译为Java
代码供Android
设备使用,最后,在Google Web Toolkit
(一个Google
开发的可以使用Java
语言开发Web
应用的工具集)的帮助下,又将这些Java
代码转译为了Web
平台可用的JavaScript
代码。但最后生成的Web
应用代码库十分庞大,且在浏览器中的运行性能并不可观。这个「粗糙版」的Web
应用发布于2014
年。2015
年通过Asm.js
将原有的C++
代码中的主要功能直接进行编译移植到到Web
平台,性能有了很大的提告。2018
年3
月,基于Wasm
构建的AutoCAD Web
也成功诞生,https://web.autocad.com/login。-
Google
地球最初使用C++
语言在Windows
平台上开发。后来移植到了Android
和iOS
平台中。2017
年4
月18
日,经过全新设计的Google
地球9.0
发布。由于采用了 Native Client 技术,刚发布时仅能在Chrome
中运行。2020
年2
月27
日,Google
使用C++
语言通过WebAssembly
上重写了Google
地球,从此Google
地球可以在Firefox
和Edge
上运行。 bilibili 上传视频的封面
在 知乎 看到的一个回答。
投稿视频的时候,当你的视频还在上传中,已经可以自由选择AI推荐的封面。这里采用了webassembly+AI的前端整合。
webassembly 负责读取本地视频,生成图片;
tensorflow.js 负责加载AI训练过的 model,读取图片并打分。
从完全的服务端架构 => 前端架构 && 服务端兜底。
webassembly支持解析99%以上的视频编码格式,速度提升体验惠及约50%的web投稿用户。
作者:Stois Fu
链接:https://www.zhihu.com/question/265700379/answer/951118579
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
由于当前 Wasm
标准下,Wasm
模块不能直接操纵 dom
元素,所以 WebAssembly
主要应用在了一些计算密集型的场景下,视频的解码编码、图像处理、涉及到复杂计算的算法、加密算法等等。
不止于Web
Wasm
除了应用在浏览器中,也可以应用到 out-of-web
环境中。通过 WASI
(WebAssembly System Interface
,Wasm
操作系统接口)标准,Wasm
可以直接与操作系统打交道。通过已经在各种环境实现了 WASI
标准的虚拟机,我们就可以将 wasm
用在嵌入式、IOT 物联网以及甚至云,AI 和区块链等特殊的领域和场景中。
有了 WASI
标准,文章最开始介绍的当前应用的架构在未来可能会发生质的改变。
上边架构的最大问题就是各个操作系统不能兼容,同一个app
需要采用不同的语言在不同平台下各实现一次。
比如一款 A
应用,如果想实现跨平台的话,我们需要用 java
完成在安卓上的开发,用 Objective-C
实现 iOS
上的开发,用 C#
实现 PC
端的开发… …也就是下边的样子。
但如果有了 wasm
,我们只需要选择任意一门语言,然后编译成 wasm
,就可以分发到各个平台上了。
这也是 Wasm
官方宣传的 Ending
定律,Any application that can be compiled to WebAssembly, will be compiled to WebAssembly eventually.
总结
此时回顾一下,WebAssebmly
的定义,应该会清晰很多了。
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
它不是一种语言,而是规定了一种虚拟指令集,可以作为各个语言的编译目标,然后通过 wasm
的虚拟机运行到浏览器还有其他各个平台中。
对于前端领域,当前 Webassembly
在某些场景下可以有效提高前端项目的性能,并且可以将 C/C++
领域的一些优秀的库通过编译直接运行到浏览器中。如果前端遇到了性能的问题,不妨可以考虑下 WebAssmbly
的方案。
参考链接
极客时间WebAssembly入门课,很系统,强烈推荐