1

定义

先看官网给的定义。

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客户端和服务端的应用中,一句话总结就是运行在Web平台上的Assembly。

历史

回顾下WebAssembly诞生的原因:

众所周知,js是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于我们开发者,确实很方便,但对于运行它的引擎就很有问题了。
image.png
由于 js 的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以V8引擎采用了JIT(即时编译技术)技术,监控一些经常执行的代码,将其编译成CPU直接执行的机器码,提高执行速度。但在某些情况下还得反优化,回到字节码进行执行。

随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。

WebAssembly于2019/12/05成为万维网联盟(W3C)的推荐标准,与HTML、CSS和JavaScript一起成为Web的第四种语言。

兼容性

image.png

使用方法

从.wasm源文件到实例化的对象主要有三个步骤,加载->编译->实例化->调用

  • 加载:读取.wasm字节码,一般通过fetch从网络中获得。
  • 编译:在Worker线程编译成平台相关的代码。
  • 实例化:将宿主环境的一些对象、方法导入到wasm模块中。
  • 调用:通过上一步已经实例化的对象,来调用wasm模块中的方法。

API
WebAssembly.instantiate(source, importObject)
WebAssembly.instantiateStreaming(resp, importObject)

  • source: 含有效Wasm模块二进制字节码的ArrayBuffer或TypedArray对象。
  • resp: 调用fetch后的response对象,resp.ArrayBuffer() -> source。
  • importObject: 要导入到Wasm模块中的对象。

API方法封装了编译和实例化两个步骤,在调用后将加载的字节码转成已经实例化的instance,可以通过instance对象调用模块方法。

实践

先写个C++程序fibonacci.cc,斐波纳契数字的递归写法。

#include <emscripten.h>
extern "C" {
  EMSCRIPTEN_KEEPALIVE 
  int fibonacci(int n) {
    if(n < 2) {
      return 1;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

然后安装Emscripten用来将C++程序编译为wasm格式。
(安装方法也可自行google,如果想省去安装步骤可以去https://github.com/thjjames/wasm下载)

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install latest
$ ./emsdk activate latest
$ source ./emsdk_env.sh

安装后执行下边的命令,

emcc fibonacci.cc -s WASM=1 -O3 --no-entry -o fibonacci.wasm

-s WASM=1 表明编译成Webassembly的程序;
-O3 表明编译的优化程度;
–no-entry 参数告诉编译器没有声明 main 函数;
-o 指定生成的文件名。

fibonacci.htmlfibonacci.wasm文件放在任意项目根目录下,打开ip:port/fibonacci.html查看(fetch只能读取https?地址,如果直接打开file类型会报跨域)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>fibonacci测试</title>
  </head>
  <script>
    function fibonacciJS(n) {
      if (n < 2) {
        return 1;
      }
      return fibonacciJS(n - 1) + fibonacciJS(n - 2);
    }
    const response = fetch('fibonacci.wasm');
    const numArr = [10, 20, 30];
    WebAssembly.instantiateStreaming(response).then(
      ({ instance }) => {
        const { fibonacci } = instance.exports;
        numArr.forEach(num => {
          let consoleTable = {};
          let ccTime = 0;
          let jsTime = 0;
          for (let time = 0; time < 10; time++) {
            let start = performance.now();
            fibonacci(num);
            ccTime += (performance.now() - start);

            start = performance.now();
            fibonacciJS(num);
            jsTime += (performance.now() - start);
          }
          consoleTable[`wasm模块调用fibonacci数字${num}时间(平均)`] = `${ccTime / 10}ms`
          consoleTable[`js模块调用fibonacci数字${num}时间(平均)`] = `${jsTime / 10}ms`
          console.table(consoleTable);
        })
      }
    )
  </script>
  <body>
  </body>
</html>

Chrome打印结果如下图
image.png

可以看到wasm很明显的提高了运行速度,时间稳定在js的一半以内!

应用

WebAssembly在eBay的实践:速度提升50倍
基于WebAssembly的H.265播放器

总结

对于前端领域,当前Webassembly在某些场景下可以有效提高前端项目的性能,并且可以将C/C++领域的一些优秀的库通过编译直接运行到浏览器中。


小皇帝James
600 声望7 粉丝

IT吴彦祖