WebAssembly入门-未来可能发生的巨变

引言

先看下 官网 给的定义。

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
2
3
4
5
6
7
int add_a_and_b(int a, int b) {
return a + b;
}

int main() {
return add_a_and_b(2, 3);
}

所对应的汇编就是下边的样子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_add_a_and_b:
push %ebx
mov %eax, [%esp+8]
mov %ebx, [%esp+12]
add %eax, %ebx
pop %ebx
ret

_main:
push 3
push 2
call _add_a_and_b
add %esp, 8
ret

这里的 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
2
3
int f(int i) {
return i + 1;
}

经过编译器编译会生成下边的 js 代码。

1
2
3
4
function f(i) {
i = i|0;
return (i + 1)|0;
}

注意这里的|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
2
3
4
5
6
7
8
9
10
#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
if(n < 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}

函数的定义置在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 	
00000000: 00 61 73 6D 01 00 00 00 01 11 04 60 00 01 7F 60 .asm.......`...`
00000010: 01 7F 01 7F 60 00 00 60 01 7F 00 03 07 06 02 01 ....`..`........
00000020: 00 03 01 00 04 05 01 70 01 02 02 05 06 01 01 80 .......p........
00000030: 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 ........A..@....
00000040: 41 84 08 0B 07 88 01 09 06 6D 65 6D 6F 72 79 02 A........memory.
00000050: 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E ..__indirect_fun
00000060: 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 09 66 69 ction_table...fi
00000070: 62 6F 6E 61 63 63 69 00 01 0B 5F 69 6E 69 74 69 bonacci..._initi
00000080: 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F alize...__errno_
00000090: 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B location...stack
000000a0: 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 Save...stackRest
000000b0: 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 ore...stackAlloc
000000c0: 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 ...__data_end...
000000d0: 07 01 00 41 01 0B 01 00 0A 66 06 03 00 01 0B 3D ...A.....f.....=
000000e0: 01 02 7F 41 01 21 01 20 00 41 02 4E 04 7F 41 00 ...A.!...A.N..A.
000000f0: 21 01 03 40 20 00 41 7F 6A 10 01 20 01 6A 21 01 !..@..A.j....j!.
00000100: 20 00 41 03 4A 21 02 20 00 41 7E 6A 21 00 20 02 ..A.J!...A~j!...
00000110: 0D 00 0B 20 01 41 01 6A 05 41 01 0B 0B 04 00 23 .....A.j.A.....#
00000120: 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B ......$....#...k
00000130: 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B Apq".$......A...

让我们来解读下,最开始的前八个字节 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(type (;2;) (func))
(type (;3;) (func (param i32)))
(func (;0;) (type 2)
nop)
(func (;1;) (type 1) (param i32) (result i32)
(local i32 i32)
i32.const 1
local.set 1
local.get 0
i32.const 2
i32.ge_s
if (result i32) ;; label = @1
i32.const 0
local.set 1
loop ;; label = @2
local.get 0
i32.const -1
i32.add
call 1
local.get 1
i32.add
local.set 1
local.get 0
i32.const 3
i32.gt_s
local.set 2
local.get 0
i32.const -2
i32.add
local.set 0
local.get 2
br_if 0 (;@2;)
end
local.get 1
i32.const 1
i32.add
else
i32.const 1
end)
(func (;2;) (type 0) (result i32)
global.get 0)
(func (;3;) (type 3) (param i32)
local.get 0
global.set 0)
(func (;4;) (type 1) (param i32) (result i32)
global.get 0
local.get 0
i32.sub
i32.const -16
i32.and
local.tee 0
global.set 0
local.get 0)
(func (;5;) (type 0) (result i32)
i32.const 1024)
(table (;0;) 2 2 funcref)
(memory (;0;) 256 256)
(global (;0;) (mut i32) (i32.const 5243920))
(global (;1;) i32 (i32.const 1028))
(export "memory" (memory 0))
(export "__indirect_function_table" (table 0))
(export "fibonacci" (func 1))
(export "_initialize" (func 0))
(export "__errno_location" (func 5))
(export "stackSave" (func 2))
(export "stackRestore" (func 3))
(export "stackAlloc" (func 4))
(export "__data_end" (global 1))
(elem (;0;) (i32.const 1) func 0))

上边的格式属于 「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
2
3
4
5
6
7
8
#include <emscripten.h>

extern "C" {
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
}

对应的 .wasm 字节码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60
00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01
7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02
02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90
88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D
65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65
63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C
65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69
61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F
6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B
53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74
6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63
00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09
07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07
00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00
24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24
00 20 00 0B 05 00 41 80 08 0B

然后直接在控制台输入下边的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WebAssembly.instantiate(new Uint8Array(`
00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60
00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01
7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02
02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90
88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D
65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65
63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C
65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69
61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F
6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B
53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74
6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63
00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09
07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07
00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00
24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24
00 20 00 0B 05 00 41 80 08 0B`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(({instance}) => {
const { add } = instance.exports
console.log('2 + 4 =', add(2, 4))
})

然后就会看到输出了 2 + 4 = 6。

Web API 尝试

我们再尝试一下流式编译。直接使用之前的斐波纳契数字的 fibonacci.wasm 模块。

首先我们需要提供一个简单的 HTTP 服务,用来返回 .wasm 文件。

新建一个 node.js 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const http = require('http');
const url = require('url');
const fs = require('fs');
const path =require('path');

const PORT = 8888; // 服务器监听的端口号;

const mime = {
"html": "text/html;charset=UTF-8",
"wasm": "application/wasm" // 当遇到对 ".wasm" 格式文件的请求时,返回特定的 MIME 头;
};

http.createServer((req, res) => {
let realPath = path.join(__dirname, `.${url.parse(req.url).pathname}`);
// 检查所访问文件是否存在,且是否可读;
fs.access(realPath, fs.constants.R_OK, err => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end();
} else {
fs.readFile(realPath, "binary", (err, file) => {
if (err) {
// 文件读取失败时返回 500;
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end();
} else {
// 根据请求的文件返回相应的文件内容;
let ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
let contentType = mime[ext] || "text/plain";
res.writeHead(200, { 'Content-Type': contentType });
res.write(file, "binary");
res.end();
}
});
}
});
}).listen(PORT);
console.log("Server is runing at port: " + PORT + ".");

然后来编写我们的 html 文件,讲到斐波那契数字,我们顺便做一个性能的测试,来比较一下使用 wasm 的方式和原生 js 的求解速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>斐波纳切数字</title>
</head>
<script>
function fibonacciJS(n) {
if (n < 2) {
return 1;
}
return fibonacciJS(n - 1) + fibonacciJS(n - 2);
}
const response = fetch("fibonacci.wasm");
const num = [5, 15, 25, 35, 45];
WebAssembly.instantiateStreaming(response).then(
({ instance }) => {
let { fibonacci } = instance.exports;
for(let n of num) {
console.log(`斐波纳切数字: ${n},运行 10 次`)
let cTime = 0;
let jsTime = 0;
for(let time = 0; time < 10; time++) {
let start = performance.now();
fibonacci(n)
cTime += (performance.now() - start)

start = performance.now();
fibonacciJS(n)
jsTime += (performance.now() - start)
}
console.log(`wasm 模块平均调用时间:${cTime / 10}ms`)
console.log(`js 模块平均调用时间:${jsTime / 10}ms`)
}

}
)
</script>
<body>
</body>
</html>

然后执行 node node.js 开启 http 服务,接着在浏览器中打开 http://localhost:8888/index.html,控制台中输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
斐波纳切数字: 5,运行 10 次
index.html:34 wasm 模块平均调用时间:0.001499993959441781ms
index.html:35 js 模块平均调用时间:0.005500001134350896ms
index.html:22 斐波纳切数字: 15,运行 10 次
index.html:34 wasm 模块平均调用时间:0.005999993300065398ms
index.html:35 js 模块平均调用时间:0.15650001005269587ms
index.html:22 斐波纳切数字: 25,运行 10 次
index.html:34 wasm 模块平均调用时间:0.6239999900572002ms
index.html:35 js 模块平均调用时间:1.1620000121183693ms
index.html:22 斐波纳切数字: 35,运行 10 次
index.html:34 wasm 模块平均调用时间:70.59700000681914ms
index.html:35 js 模块平均调用时间:126.21099999523722ms
index.html:22 斐波纳切数字: 45,运行 10 次
index.html:34 wasm 模块平均调用时间:8129.7520000021905ms
index.html:35 js 模块平均调用时间:16918.658500007587ms

整理成表格看一下:

可以看到 wasm 很明显的提高了运行速度,运行时间稳定在 js 的一半,当规模达到 45 的时候,wasm 的运行时间比 js 少了整整 8 秒。

这里也可以看出,如果对于计算密集型的应用,wasm 可以大展身手了。

前端应用

来看一些目前已经成功落地的 WebAssembly 的应用。

  1. 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倍。

  2. 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。

  3. 谷歌地球

    Google 地球最初使用 C++ 语言在 Windows 平台上开发。后来移植到了 Android 和 iOS 平台中。2017 年 4 月 18 日,经过全新设计的 Google 地球 9.0 发布。由于采用了 Native Client 技术,刚发布时仅能在Chrome 中运行。2020 年 2 月 27 日,Google 使用 C++ 语言通过 WebAssembly 上重写了 Google 地球,从此 Google 地球可以在 Firefox 和 Edge 上运行。

  4. 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 现状与实战

WebAssembly: another JVM?

Why WebAssembly is a Big Deal

Awesome WebAssembly Languages

极客时间WebAssembly入门课,很系统,强烈推荐

汇编语言入门教程

WebAssembly在eBay的实践:速度提升50倍

windliang wechat