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的过程可以归纳为三步:
- 使用强类型语言(如C++)编写代码
- 用工具将代码转换成wasm文件
- 在页面中加载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程序,这或许会让前端开发迎来一轮新的变革。