探究webassembly(2):性能探究和实验分析

1.编译器和解释器

编译器通常是在程序执行之前,把用户写好的把高级语言翻译成低级语言,得到可执行代码,如果要得到执行结果,还得经过链接器

解释器我偏向于理解为虚拟机加编译器,即在程序执行的时候,把源码即时翻译为抽象语法树或者字节码,实时得到执行结果

2.JUST-IN-TIME机制

通常来讲, 编译器拥有更多的时间对代码进行优化,同时它把翻译的过程提到程序运行之前,所以执行效率要高于解释器,至于解释器的好处,我认为和JS在Web领域大行其道的原因是一致的,那就是对不同系统平台间有优秀的兼容性,同时并不追求极致的性能。而随着对性能要求的越来越高,just-in-time(JIT)类型的编译器便出现了。

JIT通过监视运行,面对需要大量调用的部分代码进行了优化,JIT机制在一定程度上兼顾了编译器和解释器的优点,让JS的运行效率有了指数级的提升,为各种消耗大量性能的应用登上H5提供了可能,但是JS所使用的动态类型机制造成了大量的额外工作,这一部分消耗在很多情况下并没有办法通过优化解决,如果有兴趣可以详细的了解JIT机制,另外每个浏览器对JIT都有不一样的实现,使得开发者没有办法更好的利用这一机制。

3.asm.js

  Emscripten项目实现了把C/C++转换成 JS,而asm.js就这样产生了,asm.js是JS的一个子集,是作为JS的汇编语言来设计的,并且克服了JS变量弱类型的弱点,遗憾的是asm.js只有FIREBOX支持,其实一路看下来总觉得asm.js有点是webassembly前身的意味,包括asm项目用的工具emscripten也沿用至今,webassembly则有IE,CHROME,FIREBOX和SAFIRE的支持,几乎云集了浏览器的大佬,这也让它从出生的时候档次就高了不少。

4.webassembly和JUST-IN-TIME机制下的JS优势对比

优势:

一:体积小,一般webassembly文件比传统JS文件要小20%左右,在服务端加载的时候速度自然就快了20%

二:解析快,JS需要经过浏览器解析成抽象语法树和字节码,而webassembly不需要

三:编译和优化,webassembly作为强类型的语言当然比JS这种弱类型的语言有优势

四:面对开发者,开发者不需要了解每一个浏览器对JIT进行不同优化的步骤,这方便了开发者自行对代码进行优化,同时开发者在进行Web开发的时候可以利用自己熟悉的语言进行开发,这也加快了开发效率以及代码优化度。

目前很多浏览器还不支持Webassembly ,这时候可以采取回退到JS的机制来保证代码正常运行。


5.webassembly和js的小实验

      我利用Emscripten和Chrome完成了几个小实验,在一开始我的设想是用C++来完成这些实验的,但是发现JS调取不到我写的函数,我找到两个原因,一个是emscripten在优化的时候会自动把没有用到的函数删除,所以我们需要在emcc的时候把函数添加进去

 

./emcc -s EXPORTED_FUNCTIONS="['_main', '_my_func']"  ...


或者在代码里面这样定义函数


void EMSCRIPTEN_KEEPALIVE yourCfunc() {..}


另外C++的函数因为在编译的时候会更改函数名(我搜索过编译完成的JS文件,函数名给添加了几个字母进去)在这一方面官网似乎也没有解决办法,只是建议使用C函数,所以最后我只能写了C函数做测试,值得一提的是,我在C函数里面用了printf但是并没有成功执行到chrome的控制台里,在main里面执行是有打印的,如果你知道为什么,请在评论下告诉我,谢谢。


接下来我介绍一下我的几个小实验,采用的是EMCC O2的优化(O3会报错),直接编译成JS胶着代码,而不是WASM,采取的是JS调取函数的方式,下一次实验我会用共享内存的方式


在一开始我写了一个这样的函数


void calcuateFun() {
for(int a=0;a<99999999;a++)
{
int c=a*a;
}
}


我通过获取函数调用前以及调用后的js时间戳来得知它的执行情况,同时在原生JS代码中写入相同的函数来做对比,但我非常尴尬的发现执行时间是0毫秒,而原生JS需要几百毫秒,我认为这并不是真实的情况,wo 所以我又写了下面的这个函数


int calcuateFun() {
int c=0;
for(int a=0;a<99999999;a++)

{

c=a*a;

}

return c;

}


但是当我在js用
var a=_calcuateFuneee();
console.log("aaaaa:"+a)


的时候,是可以成功打印出a的,证明函数是有执行的,但是时间为0我认为并不科学,所以我认为是emscripten编译器在执行代码前就优化了代码,在还没有执行前就已经把计算结果处理好了,导致了执行时间为0,在这种情况下可以完爆JS,但并不是我想知道的结果。


我又写了这样的一个函数进行测试


int calcuateFuneee(int cccc) {
int b=999;
double c=cccc;
for(int a=0;a<c;a++)
{
c+=cccc/c;
}
return c;
}


因为这个函数需要传入一个变量计算,编译器不可能在执行前就得知运算结果,除以c是为了防止过大,过大的情况下js会打印出一个负数,同时我在js代码里面写入了一个相同的函数,传了一个比较大的值进去,这是某一次的运算结果,调用C代码用了125毫秒,而JS代码用了131毫秒,几乎看不到差距


我又换了另一种传值方法


for(var a=0;a<10000;a++)
{
    var c=_calcuateFuneee(a);
}


在JS里面我用了这样子去调用,这时候webassembly花费的时间甚至比JS还多643:623,这证明了在调用C函数的时候会消耗大量性能,好吧,接下里我试了一下使用EMSCRIPTEN_BINDINGS来调用 发现时间稍微快了点 和JS调用的时间比大概是613:636

  最后 我使用了共享内存的方式去调用,时间比是247:633,大概有三倍的提速,必须强调的是,这个结果是在排除了所有干扰后得出的,在纯计算的领域上这应该算是让人兴奋的了,但是实际上要把结果转成数据流这些都会消耗一定性能,但这些相信在未来是可以克服的


版权声明:本文为a986597353原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。