新世界的大门:WebAssembly

性能

JavaScript的发展可以说是黑科技不断,像各种NB的Canvas应用呀、WebGL呀可以说即便是现在我有时候都在想一个网页居然可以实现这么多功能,甚至tensorflow都这种深度学习框架都有js的版本。在node的加持之下JavaScript已经可以作为一个大一统语言了。但是我们不得不面对一个事实js的运行是基于解释器运行的,意味着js的执行是解释器翻译一行然后转为底层再告知给CPU,然后再下一行这就不得不面对一个根本的难题,性能!

为了解决这个问题就要说一下我们今天的主角WebAssembly了,这个东西到底是什么呢?为什么大家都说它性能好同时也有很多反对的声音呢?我们下面从这几个方面来谈谈为什么WebAssembly搞定了性能问题。

编译方式

上面已经说了js在解释器之中的执行逻辑,这种逻辑的缺点就是他不是像其他编译型语言一样,他在每一次执行都会有一个前置的行为这必然不适合高性能运行,常规的还好一旦是多线程程序或者说是循环结构的代码,那么性能的差距一定会非常大。WebAssembly的好处是在于将js进行一次预编译,这次编译之后的东西可以简单的理解为汇编或者说是更接近底层的编译,CPU在执行的时候可以根据这些东西更快的执行对于的逻辑程序。同时这些代码不是从JS来的,而是使用底层语言进行编写然后通过编译器编译为WASM然后直接作用于CPU,一般来讲手写一个WASM的可行性并不高我使用的方式是 Emscripten 进行编译C/C++代码为JS然后再我们的JS逻辑之中使用。

内存管理

大家都知道任何一个程序员再写JS代码的时候都不会进行内存的管理,可以说一旦不管内存那么编程的难度降低了一大截。这也就是为什么大多数程序员想要入门JS之内的语言都较为简单。但是在降低开发难度的同时也造成了一个不可回避的问题。内存这个东西你要是不管必然会有一个东西来帮你处理,不可能有任何一个程序没有内存的写入与读取。这就是JS的垃圾回收机制,JS在运行的生命周期之中会自动进行内存的管理,但是程序不会智能到去管什么时候回收比较好,很可能在我们的程序执行非常重要的时候进行内存的回收,这就是大多数卡顿的根本原因。

那么 WebAssembly是怎么处理垃圾回收的呢?这个处理非常粗暴那就是不处理,对于从来没有接触过底层开发的程序员来讲这无疑是一件十分痛苦的事情,但是对于C/C++开发的人员来讲这就是性能的保障,一旦可以自行对每一个内存使用手动控制那么意味着回收的时候必然是程序员自行发起的,这导致没有其他的程序来进行内存管理,我们的CPU空闲会增加,而且可以避免程序在资源紧张的时候还要去执行回收。

如何使用

使用起来就比较简单了,我们使用的是Emscripten,好像我只找到这个东西可以编译,而且即便是UE4也是使用的它,大家可以在很多地方找到这里还是简单的描述解释一下其中最重要的是双向调用,一旦建立双向调用基本上就够使用了,当然还是需要执行一些编译的范围与执行调用的机制,这些内容比较多而且到处都是大家就自行寻找吧。

安装

首先是Emscripten官方的GIT地址:GIT地址,点击就可以进入到官方的GIT首页,里面有安装的说明,这里也简单的写一下首先是下载:

git clone https://github.com/juj/emsdk.git

cd emsdk

git pull

emsdk install latest

然后是编译安装,这里同时将Emscripten放到环境变量之中:

.\emsdk\emsdk activate latest

.\emsdk\emsdk_env.bat

JS调用C/C++函数

JS调用C/C++比较简单其实就是对一个已经注册的函数调用而已,C++代码如下:

extern "C" {

int int_sqrt(int x) {
  return sqrt(x);
}}

直接编辑是不行的,需要使用EXPORTED_FUNCTIONS将函数给暴露出来,其中的function.cpp就是我们的要编译的CPP文件。

./emcc function.cpp -o function.html -s EXPORTED_FUNCTIONS='["_int_sqrt"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'

编译完成之后就可以使用 cwrap()或者ccall()进行调用如下:

//cwrap()
int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12)
int_sqrt(28)
//ccall()
var result = Module.ccall('int_sqrt','number',['number'],[28]);

C / C ++调用JS

这里的几种调用方式我直接写在代码里面了,基本能够说明问题我就不解释了。

//直接运行JS语句
emscripten_run_script("alert('hi')");

//注册JS函数然后运行
EM_JS(void, call_alert, (), {
    alert('hello world!');
    throw 'all done';
});

int main() {
    call_alert();
    return 0;
}
//内联调用JS语句
int main() {
    EM_ASM(
        alert('hello world!');
        throw 'all done';
    );
    return 0;
}

//传值调用
int x = EM_ASM_INT({
    //$0是传入的变量,100是传入的值
    console.log('I received: ' + $0);
    return $0 + 1;
}, 100);
printf("%d\n", x);//X是101

总结

这次的总结可以说和标题一样,真正意义上的打开新世界大门。我发现她还不是因为别人说的,而是我发现很多基于WebGL运行的网页性能都很差,无论是功能性还是表现能力还是帧数都差了桌面很多,但是UE4打包出来的HTML5应用可以做到完整的功能级别同时帧数没有什么区别,这就很NB了,之后才发现Emscripten进而找到了WebAssembly和ASM.JS,所以保持一个观察的心态还是非常重要的。关于ASM.JS之后有机会可以拿出来讲讲,原理与 WebAssembly 一直但是兼容更好性能稍微差一点。

新世界的大门:WebAssembly》有4条留言