本文发自 http://www.binss.me/blog/try-webassembly/,转载请注明出处。

工作了,能用来折腾了时间少了。眼看书签列表越来越长,今天花时间处理了一下。处理过程中发现一种名为 WebAssembly 的技术,经过几年的发展已经逐渐成熟,主流浏览器也都提供了支持。初略了解了一下该技术,以文记之。

什么是 WebAssembly

做过 Web 开发的同学都知道,在 Web 的世界中,以 html 为骨架、css 为样式,而控制器则由 JavaScript 负责实现,比如用户按下一个按钮我们想弹个窗,那么我们就可以用 javascript 写个:

document.getElementById("btn").addEventListener("click", function() {
    alert("hello");
}, false);

可以说一直以来,JavaScript 是我们和浏览器打交道、控制其行为的唯一通道。但 JavaScript 有很多为人诟病的地方,比如说性能差,性能差,还有性能差,JIT 也救不了那种。当然,还有语言本身无数的坑,写过的都躺过坑,ESxx 你今天学了吗。

于是各家都看不下去了,提出了自家的方案:

  • Microsoft: 搞个强类型语言 TypeScript ,能够编译成 JavaScript ,提高代码健壮性
  • Google: 搞个强类型语言 Dart ,能够编译成 JavaScript ,提高代码健壮性。也支持在原生虚拟机上执行
  • Firefox :搞个 JavaScript 的子集 asm.js ,可通过标注加上类型,提高性能

有没有更好的办法来做这事呢?各大巨头们脑袋一拍,搞出一个 WebAssembly 。

个人理解, WebAssembly 不是一门编程语言,是一种新的字节码格式。该字节码和架构无关,但能够快速被翻译成相应架构的机器码,因此执行速度飞快。最骚的是其允许将高级语言,如 AssemblyScript、C/C++、Golang 等写成的程序编译成 WebAssembly 字节码,然后放到网页中加载执行了。哇,C/C++ 程序员也有能写 Web 的一天了,岂不美哉。

为什么 WebAssembly 会比 Javascript 快?

在浏览器上,一份代码从加载到执行,主要经历以下阶段:

  1. 获取:将代码下载下来。对于 JavaScript 来说,就是裸的代码。而对于 WebAssembly 来说,是编译好的字节码。即使经过压缩,JavaScript 往往还是比 WebAssembly 大
  2. 解析:JavaScript 代码下载下来后需要建立 AST,生成 IR。而 WebAssembly 早就生成好了
  3. 编译:JavaScript 需要编译。而 WebAssembly 早就由更高级的编译器(如 LLVM)料理好了,无需再进行类型推断和其他优化工作,只需进行简单的到目标架构的字节码转换
  4. 重优化:JavaScript 在 JIT 下特有的阶段:当上下文有变动、代码被多次执行时,JIT 决定重新编译这段代码,带来了额外的开销
  5. 运行:这部分的效率依赖于编译器的功力,一般来说 WebAssembly 生成的机器指令往往更优,运行效率更高
  6. 垃圾回收:JavaScript 的 GC 由浏览器来做,而 WebAssembly 需要开发者自己管理内存,所以无需进行 GC

综上所述,WebAssembly 比 Javascript 快。

实验

由于 WebAssembly 标准和工具链变化很快,网上很多文章都已经 out of date 了,比如 binaryen 已经移除了 s2wasm 。为此自己瞎折腾了一番。

首先我们需要一份 C/C++ 代码,主要参考自 https://github.com/richardanaya/llvm-8-wasm

// main.c
// a simple way to export your functions
#define export __attribute__((visibility("default")))

// you would use this variable to write your own malloc/free
extern unsigned char __heap_base;

export void *get_heap_base() { return &__heap_base; }

export int add(int x, int y) { return x + y; }

主要需要将变量和函数暴露出去,才能在浏览器中调用。

随后用 LLVM 进行编译、链接:

clang main.c -S -emit-llvm --target=wasm32 -omain.ll
llvm-link -o main.bc main.ll
opt -O3 main.bc -o main.bc
llc -mtriple=wasm32-unknown-unknown -O3 -filetype=obj main.bc -o main.o
wasm-ld main.o -o main.wasm --no-entry --export-dynamic -allow-undefined

得到 WebAssembly 的 Binary :main.wasm 。

然后我们写个 html 加载它:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Hello WebAssembly</title>
  <script>
     if ('WebAssembly' in window) {
        fetch('main.wasm')
            .then(response => response.arrayBuffer())
            .then(buffer => WebAssembly.compile(buffer))
            .then(module => {
              const instance = new WebAssembly.Instance(module);
              console.log(instance.exports.__heap_base);
              console.log(instance.exports.get_heap_base());
              document.getElementById("btn").addEventListener("click", function() {
                var lvalue = document.getElementById("lvalue").value;
                var rvalue = document.getElementById("rvalue").value;
                document.getElementById("result").setAttribute('value', instance.exports.add(lvalue, rvalue));
              }, false);
            });
      } else {
        output.value = "Your browser doesn't support Web Assembly. You may need to enable it in your browser's flags.";
      }
  </script>
  </head>
<body>
  <input type="text" id="lvalue" value="1"/> +
  <input type="text" id="rvalue" value="2"/> = 
  <input type="text" id="result" disabled/>
  <input type="button" id="btn" value="Run" />
</body>
</html>

然后用 python 起个 web server 加载之:

python -m SimpleHTTPServer 8000

可以发现 main.wasm 被加载,在 console 输出了内容:

然后我们点击 Run 按钮,调用我们 C 实现的 add 函数来实现加法:

More

WebAssembly.instantiate 函数支持以第二个参数传参给 callee ,比如传个 windows.alert 那么在 C 代码中就可以调用之来弹窗,然而网上没找到 C 版本的用法。

展望

JIT 的引入使 Web 进入了黄金发展期,得益于性能的提升,开发者们极大地丰富了网页的功能和样式。 WebAssembly 的诞生无疑又是一股春风。

以上的小 demo 能成功在 Mac OS Chrome 74 / Safari 12.1 上运行,同时也能在 IOS 12.3.1 的 Safari 上运行,说明主流浏览器都已经跟上了。事实上,目前已经有一些网站已经用上了 WebAssembly 。比如某些矿池实现的浏览器挖矿,实现上就是跑 WebAssembly 来挖,不得不说也是一种需要高性能的应用场景。

参考

https://github.com/richardanaya/llvm-8-wasm

https://www.ibm.com/developerworks/cn/web/wa-lo-webassembly-status-and-reality/index.html

https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/

https://zhuanlan.zhihu.com/p/24632251