初探Webassembly

1,806 阅读6分钟

原文链接初探Webassembly

在开始聊Webassembly(下文简称为wasm)之前,咱先了解一下为什么会出现wasm这个全新的web格式。这得从Javascript的诞生说起。

Javascript的诞生

在1994年的时候,网景公司成立,同年发布了Navigator浏览器0.9版本,这个浏览器只能用来浏览,不能和用户进行相关的交互。1995年,Brendan Eich进入网景公司,花了十天的时间研究出了Mocha,之后更名为LiveScript,最后因为网景公司想趁Java的热度才更名成Javascript。

Brendan Eich主要研究的方向是函数式编程,网景公司希望研究将Scheme语言作为网页脚本语言的可能性。Brendan Eich也认同这种想法。网景公司希望网页脚本语言要与Java足够相似,并且要比Java简单,要让非专业的开发者也能上手,然而,Brendan Eich对Java谈不上一点喜欢,所以他花了10天就设计出了JavaScript。

Javascript的弊端和改进之路

由于JavaScript最初只是为了简单的在浏览器上进行简单的交互,所以Brendan Eich并没有考虑JavaScript在一些复杂场景中的情况,随着互联网的发展,js已经变成了在Web领域最广泛使用的编程语言,因此JavaScript也留下来很多缺陷,例如没有类型,异常(后来加上去了)和对象模型等等的坑,其中最大的坑当属JavaScript性能了。

JavaScript是解释型语言,并且是单线程的,所以效率比较低,执行速度很慢,而此时Web应用越来越复杂,比如在一些实时性较高的应用场景中(比如商品秒杀),JavaScript显得有心无力,于是,聪明的工程师们肯定不会置这个问题于不顾,2009年,google在chrome中引入了JIT(即时编译)技术,有了这个技术,JavaScript执行效率瞬间提升了20-40倍,随之而来催生了一批大型Web应用,随着互联网的进一步发展,JIT性能提升并不能满足日益复杂的Web应用。并且JIT身也有不足之处,例如,第一,JIT是在运行期编译,如果同一变量的数据类型变了,那么JIT就得推到从来,第二,JIT并不会去优化代码,很粗暴的方式去转译代码就完事了。第三,JIT在一些情况下无法生成代码,例如异常,for in等等.总之,JIT带来的性能提升实在有限,可能还是因为本身JavaScript的编译速度实在太慢了吧。聪明的工程师们不会轻易被这样的问题给难倒,他们不断的去探索和改进JavaScript带来的性能问题。于是,新的解决方案随之而来,其中,微软的Typescript和Mozilla的asm.js是其中的具有代表行的解决方案。

Ts方案是JavaScript的一个超集,主要是把强类型的Ts通过插件转译成JavaScript,但是本质就是让JIT变得快一些而已。asm.js是JavaScript的一个子集,加上变量类型尽可能的提升JIT的性能。Mozilla Firefox是第一个实现针对asm.js优化的浏览器,从Firefox 22开始使用。Asm.js相较于普通的js取得了极大的性能提升,但asm.js本身也存在一些问题,还不是一个比较理想的解决方案。因为asm.js和JavaScript一样,都是文本格式,对于复杂的项目而言,解析asm.js的时间也会非常长,和二进制格式根本不可同日而语。同时,因为asm.js对Math函数进行拓展和改写以及自身的相关实现也遭遇了很多的问题,为了解决这些问题,新的技术方案wasm应应势而生。

什么是Webassembly

webassembly-logo.png WebAssembly是Web浏览器的新扩展,从官网的介绍就能知道wasm是一种无版本、安全的、高效的二进制格式,他可以被调用进入上下文,也可以调用浏览器的功能,而且不仅是可以运行在浏览器上,非Web环境也可以运行。

wasm 模块总是与 JavaScript“胶水”代码一起使用,在必要的时候可以执行一些有用的操作。WebAssembly 可以看做是对JavaScript的加强,弥补 JavaScript 在执行效率上的缺陷。

WebAssembly是 C、C++、Rust、Go、Java、等语言的编译目标,经过编译器编译之后的二进制代码,无需经过Parser和 ByteCode Compiler这两步,比asm.js更快。WebAssembly强制使用静态类型,在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境,安全性更好。

Webassembly与Javascript性能对比

我们通过斐波拉契数列求和分别在wasm和Javascript中的执行速度来对比两者之间的执行效率。

我们通过AssemblyScript来编写wasm程序,AssemblyScript是一个类似于Typescript的语言,AssemblyScript环境的安装可参考官网的文档按步骤安装即可(AssemblyScript传送门。)我们使用AssemblyScript来编写一个斐波拉契数列求和的函数,代码如下:

export function fib(n: i32): i32 {
  if (n === 0 || n === 1 ) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}
复制代码

经过asbuild的构建命令即可生成出普通版本和优化版本的.wasm二进制文件,还有一个.wasm.map源码文件映射,生成的代码文件如下图所示:

屏幕快照 2021-05-19 上午11.20.35.png

Javascript斐波拉契数列求和的代码如下:

function jsFib(n){
     if (n === 0 || n === 1 ) {
         return n;
     }
     return jsFib(n - 1) + jsFib(n - 2);
}
复制代码

由于30以下的斐波拉契数计算的时间很小,所以我们取了30,35,40以及45的斐波拉契数在chrome、firefox和opera三个主流浏览器中进行计算,Javascript和wasm下的斐波拉契数在不同的浏览器中计算所花时长如下表所示(单位:ms):

c0fdedd0f788932e44ded9307f4237b5.png

从上面的数据可以看出在不同的浏览器里面虽然wasm和js对于相同的斐波拉契数执行时间都存在时间上的差异,但是wasm的执行效率是比js高的很多的。

Webassembly能替代Js吗

最后,既然wasm执行效率这么快,那他会完全替代Javascript吗?虽然 JavaScript 跟 C、C++等静态语言相比执行速度还有很大差距,但是大多数 Web 应用的性能瓶颈已经不是 JavaScript 语言本身了,反而是网络资源的加载,这一点 WebAssembly 并无优势。

而且,当要开发一个新的功能时,无论你是选择用 C、C++、Rust、Go、Java 还是 AssemblyScript,开发成本都要比JavaScript高。遇到浏览器不支持,都得降级为用JavaScript实现,这无疑增加了业务复杂性。wasm适合一些复杂运算场景,例如Google Earth和Auto CAD等Web端的应用已经由wasm实现,所以,至少目前来看,wasm不会完全替代Javascript。