WebAssembly技术展望

在做前端可视化时,JS常常要承担非常多的计算任务。例如渲染一个地图的矢量图形,可能需要对上百万个顶点数据做投影、偏移等计算,计算量无疑是非常巨大的。而JS的速度无法承受大量的计算,并且浏览器中JS是单线程的,过大的计算量会使页面长时间无法响应,使用体验非常不好。

现阶段JS主要的性能瓶颈就是它弱类型的特点,JS引擎需要花大量的时间来猜测变量的类型。令人无奈的是,JS的这一缺陷却也是JS的特性之一,要保留这一特性,就无法进一步优化。所以有人认为,JS是一门天生残疾的语言。

为了解决这一问题,业界也做了非常多的尝试。FireFox推出的asm.js可以看作是WebAssembly的前身,asm.js构造出JS的一个子集,使用特殊的语法来标记变量的类型,再配合JS引擎,实现JS性能的的优化。下图是速度对比

从图中可以看出asm.js的速度已经非常接近原生了。

而WebAssembly则做得更彻底,它直接跳过JS,使用其他强类型语言来编写代码逻辑,最后将其编译成wasm文件,在页面中加载wasm文件来执行。

对比来看,WebAssembly甚至比asm.js还快5%-7%,更重要的是WebAssembly已经得到了几乎所有主流浏览器厂商的支持,W3C也成立了专门的 Webassembly Community Group,前途可以说一片大好。

使用WebAssembly的过程可以归纳为三步:

  1. 使用强类型语言(如C++)编写代码
  2. 用工具将代码转换成wasm文件
  3. 在页面中加载wasm模块,wasm模块可以直接执行或者作为一个库被调用。

不过,talk is cheap show me the code(废话少说,放码过来)。接下来我们就来尝试一下WebAssembly对速度究竟有多大的提升。

准备工作

使用Emscripten可以将C/C++转换成WASM,Emscripten的安装可以参考官方的教程developers-guide

这个安装过程很麻烦,需要将代码下载下来再编译,我在自己的笔记本上花了近3个小时才安装完。如果只是想简单体验一下,最好试试这个C/C++转WASM的在线工具:WasmExplorer

另外还需要一个支持WebAssembly的浏览器,WebAssembly还处于试验阶段,支持它的浏览器很少,可以试试以下两个版本的浏览器

Chrome 57
FireFox 52
编写代码

使用C++来编写计算逻辑代码,主要是编写project(投影)和unproject(反投影)这两个方法

project方法用来将经纬度转换成墨卡托坐标,unproject则是将墨卡托坐标转换成经纬度。

// 参数为坐标经度和纬度,返回一个数组,表示投影后的坐标
float* project ( float lon, float lat ) {
    // 实现略...
}

// 参数为横纵坐标,返回一个数组,表示反投影后的经纬度
float* unproject ( flaot x, float y ) {
    // 实现略...
}

为了对比,我们也需要用js来编写同样的方法。

// 墨卡托投影的js版本

function project ( lon, lat ) {
    // 实现略...
}

function unproject ( x, y ) {
    // 实现略...
}
将C++文件编译成wasm

使用前面安装的Emscripten,将C++文件编译成wasm文件。执行命令:

$ emcc test/mercator.cpp

一切正常的话,就可以得到一个a.out.js文件,在页面中引入它就可以了。

当然,如果c++中写了main函数的话,可以直接编译成html文件:

$ emcc mercator.cpp -s WASM=1 -o index.html
速度测试

分别用js、C++以及WebAssembly这三种方式对100万个坐标点做投影计算。下图是测试结果:

单从结果上来看,这几种方式的速度差异并不明显。大量重复的数值计算可能并不是一个很好的测试样例,但是这也能足够说明问题了:WebAssembly在速度上确实有优势。

我理想中的使用方式是用WebAssembly来封装一个工具包,将需要大量计算的函数封装在里面,在JS代码中直接可以调用。

实际上这也是Emscripten支持的standalone方式:WebAssembly Standalone。但是从我目前的尝试来看,这样做是十分麻烦的。如果不写main函数,编译出来的wasm就不会包含include进来的库,于是像math.h之类的数学工具库都需要自己再重新实现。

最后

从现阶段看,WebAssembly的使用还是非常麻烦,如果所有逻辑代码都用WebAssembly来优化,会极大的提高开发难度,有点得不偿失。但是对于少部分应用场景,比如视频解码,图像处理,3D渲染,可视化数据处理等对运算量比较大,性能要求高的应用,WebAssembly还是非常有意义的。并且,WebAssembly的意义也不只是提升性能,它甚至可以做到直接用其他语言来编写web程序,这或许会让前端开发迎来一轮新的变革。