WebAssembly, Web 的新时代 (二)

S c r o l l D o w n

> 注: 原文刊载于《程序员》杂志 2017 年 2 月刊.

## 实战

Linux 和 Mac 平台编译原生代码到 WebAssembly 可由如下步骤实现:

### 编译环境准备

操作系统必须有可以工作的编译器工具链, 因此需要安装 gcc, cmake 环境, 此外 Python, Node.js 及 Java 环境也是需要的 (其中 Java 为可选).

如果是以其他方式安装了 Node.js, 可能需要更新 ~/.emscripten 文件的 NODE_JS 属性.

### 安装正确的 emscripten 分支

要编译原生代码到 WebAssembly, 我们需要 emscripten 的 incoming 分支. 由于 emscripten 不仅仅是用于 WebAssembly 的编译工具链, 选择正确的分支尤为重要.

### 处理安装异常

可运行 emcc -v 命令进行验证安装. 如果遇到如下错误, 表明带有 JavaScript 后端的 LLVM 编译器并未被生成.

aarch64 – AArch64 (little endian)
aarch64_be – AArch64 (big endian)
amdgcn – AMD GCN GPUs
arm – ARM

通过如下步骤解决该问题:

并且在 ~/.emscripten 文件中修改如下配置:

### 开始编译程序

现在一个完整的工具链已经具备, 我们可以使用它来编译简单的程序到 WebAssembly. 但是, 还有一些其他注意事项:

– 必须通过参数 -s WASM = 1emcc (否则默认 emcc 将编译出 asm.js) .
– 除了 wasm 二进制文件和 JavaScript wrapper 外, 如果还希望 emscripten 生成一个可直接运行的程序的 HTML 页面, 则必须指定一个扩展名为 .html 的输出文件.

在编译之前, 首先准备一个最基本的 add.c 程序.

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

int main()
{
printf(“%d”,add(1, 2));
}

按如下的命令编辑好 add.c 程序并编译:

### 运行 WebAssembly 应用

以 Chrome 浏览器为例, 如果直接在浏览器内本地打开 HTML 文件, 会有如下错误:

XMLHttpRequest 本地访问的跨域请求错误

图4: XMLHttpRequest 本地访问的跨域请求错误

由于 XMLHttpRequest 跨域请求不支持 file:// 协议, 必须经由 HTTP 实际输出, 可以由 Python 的 SimplHTTPServer 改进:

在浏览器中输入 http://127.0.0.1:8080 并打开 add.html, 就能直接看到转换成 WebAssembly 的应用程序输出结果.

### 创建独立 WebAssembly

默认情况下, emcc 会创建 JavaScript 文件和 WebAssembly 的组合, 其中 JS 加载包含编译代码的 WebAssembly. 对于 C/C++ 开发人员, 他们可能更倾向于创建独立的 WebAssembly, 用于 JavaScript 开发人员调用.

上述命令运行后我们可以得到独立的 wasm 文件. 需要说明的是, 该参数仍然在开发中, 可能随时发生规范和实现变更.

### JavaScript API 调用

从 C/C ++程序编译获得一个 .wasm 模块之后, JavaScript 开发人员可以通过如下方式进行载入 .wasm 文件并执行. WebAssembly 社区组也有计划通过 Streams 使用 streaming 以及异步编译.

最后一行调用导出的 WebAssembly 函数, 它反过来调用我们导入的 JS 函数, 最终执行 add(201700, 2), 并且在控制台获得期望的结果输出.

WebAssembly 求和函数在控制台的输出

图5: WebAssembly 求和函数在控制台的输出

## 性能

那么, WebAssembly 的真实性能如何呢? 首先我们用一直被用来作为 CPU 基准测试的斐波那契 (Fibonacci) 数列来进行对比, 这里使用的是时间复杂度呈指数增长 O(2^n) 的性能最差的递归算法, 在 Node.js v7.2.1环境下, 能够看到 WebAssembly 性能优势越发明显.

| fibonacci() | JavaScript | WebAssembly |
|:—-:|:—-:|:—-:|
|fibonacci(25) | 2.212ms | 2.055ms|
|fibonacci(31) | 11.730ms | 6.274ms|
|fibonacci(37) | 146.055ms | 68.019ms|
|fibonacci(41) | 969.209ms | 424.231ms|
|fibonacci(43) | 2485.119ms | 1007.280ms|

再看看最基本的 1000 毫秒时间内, 求和计算的运算量统计, 在同一台计算机的 Firefox 50.1.0 版本的运算结果如下:

| add() | JavaScript | WebAssembly |
|:—-:|:—-:|:—-:|
|add(1,1) | 261633 | 2118773 |
|add(10,10) | 250692 | 2109682 |
|add(100,100) | 255160 | 2104851 |
|add(12345,12345) | 256046 | 2067415 |

尽管重复测试时结果不尽相同, 重启浏览器并多次测试取平均值后依然可以看到 WebAssembly 的运算量比 JavaScript 快了近一个量级.

## Demo

Angry Bots Demo 是 WebAssembly 项目发布的一个 Demo, 由 Unity 游戏移植而来.

图6: Angry Bots Demo / Google Chrome 55.0.2883.87

通过如下方式可以体验 WebAssembly 在浏览器中的强大性能. 即便 Google Chrome 较新的稳定版也已支持 WebAssembly, 还是推荐使用 canary 版及 Firefox 的 nightly 版进行测试.

1. 下载浏览器
– Google Chrome: https://www.google.com/chrome/browser/canary.html
– Mozilla Firefox: http://nightly.mozilla.org/
– Opera: http://www.opera.com/
– Vivaldi: https://vivaldi.com/
2. 打开 WebAssembly 支持
– Google Chrome: chrome://flags/#enable-webassembly
– Mozilla Firefox: about:config -> 接受 -> 搜索javascript.options.wasm -> 设置为 true
– Opera: opera://flags/#enable-webassembly
– Vivaldi: vivaldi://flags#enable-webassembly
3. 访问 http://webassembly.org/demo/

使用 W, A, S, D 等键实现移动操作, 点击鼠标进行射击. 该 WebAssembly 游戏在浏览器中运行相当流畅, 媲美原生性能.

除了最新的浏览器开始对 WebAssembly 逐步支持外, Intel 开源技术中心开发的 Crosswalk 项目 (https://crosswalk-project.org/) 早在 2016 年 11 月初的 Crosswalk 22 稳定版 (Windows 及 Android 平台) 即已加入对 WebAssembly 实验性的支持, 开发者可以使用该版本体验 Angry Bots Demo.

## 开发者

WebAssembly 对于 Web 有显著的性能提升, 对于开发者尤其是前端或者 JavaScript 开发人员而言, 并不意味着 WebAssembly 将会取代 JavaScript.

图7: WebAssembly 与 JavaScript 引擎的关系

WebAssembly 被设计为对 JavaScript 的补充, 而不是替代, 是为了提供一种方法来获得应用程序的关键部分接近原生性能. 随着时间的推移, 虽然 WebAssembly 将允许多种语言 (不仅仅是 C/C++) 被编译到 Web, 但是 JavaScript 的发展势头不会因此被削弱, 并且仍然将保持 Web 的单一动态语言. 此外, 由于 WebAssembly 构建在 JavaScript 引擎的基础架构上, JavaScript 和 WebAssembly 将在许多场景中配合使用.

那么 WebAssembly 是不是仅仅面向 C/C++ 开发者呢? 答案依旧是否定的. WebAssembly 最初实现的重点是 C/C ++,由 Mozilla 主导开发的注重高效、安全和并行的 Rust 也能在 2016 年末被[成功编译到 WebAssembly ](/2017/01/06/rust-wasm/)了, 未来还会继续增加其他语言的支持.

在未来, 通过 ES6 模块接口与 JavaScript 集成, Web 开发人员并不需要编写 C++, 而是可以直接利用其他人编写的库, 重用模块化 C++ 库可以像使用 JavaScript 中的 Modules 一样简单.

## 进展

依据开发路线图, 2016 年 10 月 31 日, WebAssembly 到达浏览器预览里程碑. Google Chrome V8 引擎及 Mozilla Firefox SpiderMonkey 引擎都已经在 trunk 上支持WebAssembly 浏览器预览. 2016 年 12 月下旬, Microsoft Edge 浏览器使用的 JavaScript 引擎 ChakraCore v1.4.0 启用了 WebAssembly 浏览器预览支持. 而 Webkit JavaScriptCore 引擎对于该支持也在积极进行中.

目前, WebAssembly 社区组已经有初始 (MVP) 二进制格式发布候选和 JavaScript API 在多个浏览器中实现. 作为浏览器预览期间的一部分, WebAssembly 社区组 (WebAssembly Community Group) 现在正在征求更广泛的社区反馈. 社区组的初步目标是浏览器预览在 2017 年第一季度结束, 但在浏览器预览期间的重大发现可能会延长该周期. 当浏览器预览结束时, 社区组将产生 WebAssembly 的草案规范, 并且浏览器厂商可以开始默认提供符合规范的实现. 预计在 2017 年上半年, 四大主流浏览器对原生的 WebAssembly 支持将到达稳定版.

具体到 Google V8 引擎的最新进展, asm.js 代码将不再通过 Turbofan JavaScript 编译器而是编译到 WebAssembly 后, 在 WebAssembly 的原生执行环境中执行最终的机器码. 这种改变带来的好处有, 为 asm.js 将预先编译 (AOT, Ahead Of Time Compilation) 带到了 Chrome, 且完全向后兼容. 新的 WebAssembly 编译渠道重用了一些 Turbofan JavaScript 编译器后端部分, 因此能够在少了很多编译和优化消耗的前提下, 产生类似的代码. 在Google Chrome 中,WebAssembly将很快在Canary版中默认启用,开发团队也期望能够发布到2017年第一季度末的稳定版中.

## 社区

包含所有主要浏览器厂商代表的 W3C WebAssembly 社区组于 2015 年 4 月底成立. 该小组的任务是, 在编译到适用于 Web 的新的, 便携的, 大小和加载时间高效的格式上, 促进早期的跨浏览器协作. 该社区组也正在将 WebAssembly 设计为 W3C 开放标准. 目前, 除了文中所述主流浏览器厂商 Mozilla, Google, Microsoft, 及 Apple 之外, Opera 欧朋 CTO及Intel 的 8 位该领域专家均参与了该社区组. 当然, 并不是只有社区组成员才能参与标准的制定, 任何人都可以在 https://github.com/WebAssembly 做出贡献.

## 展望

由于主要的浏览器厂商对 WebAssembly 支持表现积极, 都在实现 WebAssembly 的各项功能, 因此在 Web 中高性能需求的应用例如在线游戏, 音乐, 视频流, AR 及 VR, 平台模拟, 虚拟机, 远程桌面, 压缩及加密等都能够获得接近于原生的性能. 相信 WebAssembly 将会开创 Web 的新时代.

发表评论

电子邮件地址不会被公开。 必填项已用*标注