WebAssembly是解决JavaScript 痼疾的银弹?

写在前面

   《没有银弹》是 Fred Brooks 在 1987 年所发表的一篇关于软件工程的经典论文。该论文的主要论点是,没有任何一项技术或方法能够能让软件工程的生产力在十年内提升十倍。 在 Web 开发这一领域,因为 JavaScript 一直存在着诸多从本质上来看没法解决的问题,那么解决 JavaScript 痼疾的银色子弹是否存在呢?javascript

聊聊JavaScript发展历史

    做为一门仅用了十天进行设计的语言,Brendan Eich 必定没有想到 JavaScript 会从一门简单的”脚本“发展成为 Web 开发的主宰,而且如火如荼的渗透到桌面或移动客户端开发甚至服务器端开发中。之因此 JavaScript 如此火爆,甚至有了著名的 Atwood 定律,声称"任何可使用 JavaScript 来实现的应用都最终都会使用 JavaScript 实现",主要缘由在于两点:java

  1. Web 应用能够愈来愈多的代替传统的客户端应用程序;git

  2. JavaScript 引擎的运算速度大幅改进。github

    从辩证的角度来看,上面两个观点实际上是相互影响、相互促进的关系。JavaScript 引擎运算速度的逐步提高,致使了一部分简单的客户端应用能够被 Web 应用代替,进而用户和开发者都但愿更多复杂的客户端应用也用 Web 实现,进而促进 JavaScript 引擎运算速度的进一步提高。算法

    Google V8 引擎是 JavaScript 引擎性能改善的现象级项目。V8 于 2007 年发布,因为采用了 JIT ( just-in-time)技术,V8 引擎能够将 JavaScript 代码在运行前编译为机器语言,这样运行速度就会有大幅提高。客观存在的问题是,开发者对性能的要求是近乎无止境的,而基于 JIT 的设计思路带来的性能已经逐渐被挖掘到了极限,而且逐步暴露出了一些难以解决的问题。首先是被称为“重优化”的性能瓶颈。编译为机器代码的前提条件是必须让编译器清晰的知道变量的类型,恰恰 JavaScript 是一门弱类型语言,参考以下代码:编程

function sum ( a , b ) {
    return a + b;
}
sum(1,2);
sum("1","2");

  V8 引擎在运行这段代码时,在第一次调用 sum 函数时,因为传递的类型是两个数字,因此会将 sum 这个函数的参数设置为数字类型并编译为机器码,可是紧接着,sum 函数又传递了字符串类型,这就致使编译器只能讲刚编译好的 sum 函数拆解,而后从新将其编译为参数类型为字符串类型的机器码。这种状况大大下降了 JavaScript 的运行性能。后端

    其次是由于编译器的一些优化策略可能“弄巧成拙”,致使在特定状况下性能反而有负面影响。一个典型的例子是 TypeScript 编译器在编译代码时的性能优化:浏览器

https://github.com/Microsoft/TypeScript/pull/10270 经过这个优化能够看到,经过将一个对象添加 delete 操做,强制关闭该对象的“隐藏类”机制,将一个对象切换为字典模式,达到了性能提高的做用。安全

    再次是 JavaScript 具有垃圾回收机制,虽然 V8 编译器已经对垃圾回收机制的算法进行了诸多的优化,可是在应用内存占用较大时,垃圾回收的瞬间明显仍然还有卡顿现象,这致使了复杂应用有可能出现不定时的卡顿现象。这些问题都反映出,JavaScript 这种语言机制自己的灵活性,反而限制了 JavaScript 引擎的性能优化空间,若是但愿完全解决这一问题,必然须要抛弃 JavaScript 这门语言自己,采用一门强类型的编程语言才能达到最极致的性能,在这种技术思想的指引下,WebAssembly 技术应运而生。性能优化

    提到了 WebAssembly,就必然首先说起对其有深远影响的 asm.js,这是 Mozilla 在 2013 年推出的一项新技术,它是 JavaScript 的一个子集,舍弃了大量会致使性能问题的语法,而且被设计为经过 C / C++ 代码编译生成,而非手工编写 asm.js 代码。上述的 sum 函数在 asm.js 中表现为:

function sum ( a ,b ) {
    a = a | 0;
    b = b | 0;
    return ( a + b ) | 0;
}

  上述代码中,标准的 JavaScript 引擎会对其进行解析,并生成正确的结果,而 asm.js 会根据一些不会对运行时形成计算结果错误的特殊标识对变量的类型进行声明(好比 a = a | 0 表示变量 a 是一个整数),经过这种方式,这种代码既能够在支持 asm.js 的 JavaScript 引擎上获得很高的性能,也会在不支持的设备上继续按照正确的逻辑进行执行,而非没法运行。虽然如此,asm.js 仍然存在着一些问题,主要是基于 JavaScript 语法的文本格式解析速度不够快,而且代码尺寸偏大,为了解决这些问题,将 asm.js 进行二进制化的 WebAssembly 应运而生。

WebAssembly是什么?

    WebAssembly 是一种接近机器语言的跨平台二进制格式。2017 年 3 月份,四大主流浏览器厂商 Google Chrome、Apple Safari、Microsoft Edge 和 Mozilla FireFox 均宣布已经于最新版本的浏览器中支持了 WebAssembly 的初始版本,这意味着 WebAssembly 技术已经实际落地、能够在特定生产环境进行尝试。WebAssembly 目前能够经过 Emscripten SDK 生成,下图是 WebAssembly 的编译原理:

上图展现了如何经过编写 C / C++ 代码生成 WebAssembly 内容。

  1. 首先经过 LLVM ,将 C/C++ 源代码编译为 LLVM bytecode。这 是一种跨语言的底层虚拟机字节码,理论上全部强类型编程语言都可以生成这种字节码。经过这一点能够得知,在将来理论上全部强类型编程语言(诸如 Java / C# 等)都可以开发 WebAssembly 程序。
  2. 其次经过 EMScripten 中的后端编译器,将这种抽象字节码生成 asm.js 格式的文件。这是一种特殊的 JavaScript 代码,部分 JavaScript 引擎会将这种格式以比一般的 JavaScript 代码更快的速度运行,而且因为 asm.js 仍然是 JavaScript,因此哪怕 JavaScript 引擎不支持该特性,也会以一般的方式运行这段逻辑。这意味着使用 C/C++ 编写的源代码,哪怕用户设备不支持 WebAssembly,也能够回退到 JavaScript 运行并获得一致的结果。
  3. 再次asm.js 会经过另外一个编译器生成为 WebAssembly 的 .wasm 文件,因为 WebAssembly 是二进制格式,相比 JavaScript 而言,其代码体积同比小不少,而且因为已是面向机器码的格式,也无需在运行前对源代码耗费时间进行 JIT 编译操做。

经过上述内容能够看出,WebAssembly 理论上能够经过任何强类型语言生成,不强制依赖用户的本地运行环境,代码体积小、解析速度快,几乎是 Web 开发将来的一颗“银色子弹”。惋惜的是在现阶段,WebAssembly 仍然存在着很多问题须要去解决。

  1. 首先是自身的稳定性,以 Chrome 浏览器为例,Chrome 57 支持 WebAssembly 的 MVP 版本,可是在 Chrome 58 上,大量的 WebAssembly 程序会直接致使进程崩溃,虽而后续的 Chrome 59 已经修复了绝大部分问题,可是仍然不得不对目前版本的稳定性持保留态度。
  2. 其次是可调试性,WebAssembly 被设计为了一种开放的、可调试的程序,但目前不管是 Chrome 仍是 FireFox ,在调试方面还有很大的提高空间。因为在目前阶段调试较为困难,因此用 WebAssembly 编写业务逻辑代码对研发来讲仍是很不方便的。

     还有就是与 Web 的互操做性。目前 WebAssembly 相似 WebWorker ,只能进行单纯的数值计算工做,不能在 C++ 层直接操做 DOM 节点。虽然在将来路线图中说起这一特性会在后续加入,可是在目前阶段 WebAssembly 更适合被用于更纯粹的密集型数据计算工做,而非直接编写业务逻辑。

    综上所述,在目前阶段,WebAssembly 不适合直接编写具体的业务逻辑,而更适合编写应用程序中对性能要求比较高的库,并与 JavaScript 编写的业务逻辑进行通信,并在 JavaScript 端对 DOM 节点进行操做。以笔者最近开发的白鹭引擎 5.0 的渲染库为例,白鹭引擎对外提供 JavaScript API,开发者编写的 JavaScript 逻辑代码会汇总为一组命令队列发送给 WebAssembly 层,而后 WebAssembly 负责全部的计算工做,最终生成一组基于 WebGL 格式的数据流,最后 JavaScript 对这组数据流进行简单的解析并直接调用 DOM 的 WebGL 接口传递数据。

在实践过程当中,咱们总结出 WebAssembly 的几个不容易注意的优点和缺点:

  • 代码体积很小,咱们将大约 300k 左右(压缩后)JavaScript 逻辑改用 WebAssembly 重写后,体积仅有 90k 左右。虽然使用 WebAssembly 须要引入一个 50k-100k 的 JavaScript 类库做为基础设施,可是整体来看资源尺寸的优点仍是很大的。

  • 因为代码格式是二进制、没法直接在浏览器中看到源码,尽管理论上仍然能够经过逆向工程必定程度上获得原有的业务逻辑,可是因为开发者能够在编译时使用了 -O3 等激进的优化策略,因此最终反编译获得的业务逻辑也是很难阅读的。虽然理论上一切在客户端的内容都是不安全的,可是与全部代码都直接暴露给用户相比,代码安全性获得了很大的改善。

  • 在运行 benchmark 等极限测试时,游戏引擎使用 WebAssembly 并不比 JavaScript 有几何量级的提高。笔者的推论是:因为 JavaScript 引擎的 JIT 机制会把常常运行的函数进行极限的编译优化,因此在 benchmark 这种代码大量反复执行的测试环境下,不管是 JavaScript 版本,仍是 WebAssembly 版本,运行的都是高度优化后的机器码,虽然 WebAssembly 版本仍然比 JavaScript 版有必定的性能优点,可是并不明显。

  • 在运行业务逻辑代码时,因为大部分业务逻辑代码只运行一次,因此 JavaScript 引擎只会对这部分代码进行简单的编译优化而非极限优化,因此运行这一部分代码 WebAssembly 相比 JavaScript 版本而言提高巨大,可是由于上文所述,不建议开发者在编写业务逻辑时使用 WebAssembly,因此这里陷入了一个两难。在目前而言,理想状况是除了底层库以外,部分关键的涉及性能问题的逻辑也可使用 WebAssembly 进行编写。

    综上所述,目前为止因为 WebAssembly 还不是很是完善,因此它目前的主要做用是做为 JavaScript 生态的有益补充,与 JavaScript 共存而不是取而代之。可是经过其路线图咱们能够得知,WebAssembly 的设计思想很是优秀,目前全部存在的问题从长远的角度来讲都是能够解决的问题。在加上 WebAssembly 是很是罕见的由四大浏览器厂商共同宣布会大力支持并实现的功能,其浏览器兼容性问题也终究能够获得解决,再退一步,哪怕旧式浏览器不支持,因为 WebAssembly 支持回退到 JavaScript,也能够保证正常运行。

在目前阶段,WebAssembly 适合大量密集计算、而且无需频繁与 JavaScript 及 DOM 进行数据通信的场景。好比游戏渲染引擎、物理引擎、图像音频视频处理编辑、加密算法等。

    笔者认为,WebAssembly 就像当初的 HTML5 标准同样,在公布以后最开始不被不少人看好,认为会有浏览器兼容性问题、各大浏览器厂商的实现问题、性能问题、用户需求与用户体验问题,但在近年来 HTML5 终于获得了普遍的使用,甚至有些人认为他能够在不少场景下取代 NativeApp ,而非仅仅是当年“取代 Flash”这一小目标。凭借着底层技术的跨越式发展,以及浏览器厂商的一致支持,WebAssembly 必定会有一个光明的将来,也许真的能够成为一颗 Web 开发的“银色子弹”。

转自:http://www.infoq.com/cn/news/2017/07/WebAssembly-solve-JavaScript

相关文章
相关标签/搜索