WebAssembly 介绍

我们知道 JavaScript 是一门脚本语言,具有动态类型和灵活的表达力,我们知道脚本语言通常要解释运行,这也将要消耗一些性能开销,于是 Google 在 2009 年在 V8 中引入了 JIT(Just in time compiling) 技术,把 JavaScript 运行时的性能推到了顶峰,同年利用 V8 引擎诞生了 Node.js,打开了使用 JavaScript 写后端应用的大门。对于目前写网络应用而言 JavaScript 已经足够用了,加上 Google V8 引擎能帮我们解决掉大部分问题。但是当我们把 JavaScript 应用到诸如 3D 游戏、虚拟现实、增强现实、计算机视觉、图像/视频编辑以及大量的要求原生性能的其他领域的时候,就遇到了性能问题,尤其是移动平台进一步放大了这些性能瓶颈。

而 WebAssembly 的出现就是为了解决这个问题,它是一门低级的类汇编语言,可以运行在现代网络浏览器中的新型代码并且提供新的性能特性和效果。它设计的目的不是为了手写代码,而是为了诸如 C、C++ 和 Rust 等低级源语言提供一个高效的编译目标以便它们能够在网络上运行。对于网络平台而言,这具有巨大的意义——这为客户端 app 提供了一种在网络平台以接近本地速度的方式运行多种语言编写代码的方式;在这之前,客户端 app 是不可能做到的。

WebAssembly 使用

WebAssembly 有二进制 (.wasm) 和文本 (.wast) 两种格式,并且两种格式可以互享转换,在传输和运行时使用二进制格式,而文本格式是为了阅读和开发调试所使用。

以 C 为例,首先把 C 程序编译为 WebAssembly 格式,本地编译可以查看 编译 C/C++ 为 WebAssembly 文章进行相关软件的安装,为了方便可以直接使用这款在线工具进行转换 WasmFiddle

我们先来个简单的,在 main 中返回一个数字,在 add 方法中求和

1
2
3
4
5
6
7
int main() { 
return 42;
}

int add(int a, int b) {
return a + b;
}

转换过后的 .wast 文本格式为,我们可以从这个文本看出分别 export 了 mainadd 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(module
(table 0 anyfunc)
(memory $0 1)
(export "memory" (memory $0))
(export "main" (func $main))
(export "add" (func $add))
(func $main (; 0 ;) (result i32)
(i32.const 42)
)
(func $add (; 1 ;) (param $0 i32) (param $1 i32) (result i32)
(i32.add
(get_local $1)
(get_local $0)
)
)
)

使用 Fetch 获取模块并初始化调用

1
2
3
4
5
6
7
8
fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(results => {
console.log(typeof results.instance.exports.main); // function
console.log(results.instance.exports.main()); // 42
console.log(results.instance.exports.add(1, 2)); // 3
})

我们可以使用 results.instance.exports.main() 的形式进行调用

使用 XMLHttpRequest 获取模块并初始化调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const request = new XMLHttpRequest();
request.open('GET', 'module.wasm');
request.responseType = 'arraybuffer';

request.onload = function() {
var bytes = request.response;
WebAssembly
.instantiate(bytes)
.then(results => {
results.instance.exports.main();
});
};

request.send();

目前只实现了 JavaScript 语言调用 C 语言,接下来使用 C 调用 JavaScript 方法,声明并在需要的地方调用 alert 方法

1
2
3
4
5
void alert(int a);

int add(int a, int b) {
return alert(a + b);
}

获取实例时传入 importObj 对象,把 env 环境变量注入进来

1
2
3
4
5
6
7
8
9
10
11
const importObj = {
env: {
alert: window.alert
}
};
fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, importObj))
.then(results => {
console.log(results.instance.exports.add(1, 2));
})

至此相互调用演示完成,我们可以畅想下以后 WebAssembly 普及的话,到时我们用的 React、Vue、Angular 可能就是用 C/C++ 或者其他底层语言写的。