WebAssembly的前生今世

栏目: 后端 · 前端 · 发布时间: 6年前

内容简介:上帝花了7天时间创造了世界,Brendan Eich大神在1995年花了10天时间创造了Javascript。Javascript的初衷是面向非专业编程人员和设计师的,因此大神认为不应该让这些不专业的人理解编译器这么专业的概念。这两个概念其实不太严谨,应该改为“

1.先从Javascript说起

上帝花了7天时间创造了世界,Brendan Eich大神在1995年花了10天时间创造了Javascript。 WebAssembly的前生今世

为什么Javascript被设计为解释型语言?

Javascript的初衷是面向非专业编程人员和设计师的,因此大神认为不应该让这些不专业的人理解编译器这么专业的概念。

解释型语言 vs. 编译型语言

这两个概念其实不太严谨,应该改为“ 主流实现为解释器的语言 ”和“ 主流实现为编译器的语言 ”更为妥当。事实上很多语言采用的是混合型实现,比如 Java 需要先编译成字节码,而虚拟机又是解释执行的,执行过程中又会用到JIT即时编译,因此很难说它到底属于哪一种类型的语言。实际上,编译型和解释型最大的区别在于是否保存和复用生成的目标代码(不管是存在磁盘还是内存),编译型会,而解释型则是逐条执行,用完就扔。因此,这里主要讨论编译器和解释器。

编译器的一般实现参见下图:

WebAssembly的前生今世

现代编译器一般可以分为前端和后端:前端主要负责词法分析、语法分析、语义检查等工作,并输出中间代码(为了跨平台)。而后端则负责根据把中间代码转换为目标机器代码,并进行代码优化。所以,编译器的输入是所有代码,最终输出的是目标代码,仅需编译一次,后面就可以多次运行。

解释器则略有不同,一般实现参见下图:

WebAssembly的前生今世

早期的解释器只需要经过词法分析和语法分析,生成抽象语法树AST,就可以送入“树遍历型解释器”执行并输出最终结果了。( esprima 网站上可以查看生成的AST结果)

但是后来大家发现,直接解释执行AST这种树形结构效率比较低,如果把AST转换成一种线性结构再执行效果会更好,这就是字节码。所以简单来说, 字节码就是AST的后序遍历结果 ,而执行字节码的程序则被称为虚拟机(VM)。

虚拟机的实现多种多样,最常规的做法就是一行一行地解释执行,但是如果遇到循环的话效率就比较低了,需要一遍又一遍地解释执行。当然,你也可以先编译再执行,但是由于编译优化比较耗时,如果某段代码只需要执行一遍的话,就又得不偿失了。有没有什么办法能结合这两种方式的优点呢?还真有,这就是JIT。

WebAssembly的前生今世

2.JIT(Just-In-Time)编译器

那么这个JIT编译器是怎么工作的呢?其实很简单,就是计数器 + 缓存。

刚开始,还是一行一行地解释执行,但是JIT里包含了一个 监视器(profiler) ,用于监视每行代码执行的次数。如果某一行代码执行了好几次,那么监视器就会把这行代码标记为“warm”,并送给 基线编译器(baseline compiler) 去编译。基线编译器会把该行代码编译成一个 桩(Stub) ,并分配一个“行号 + 变量类型”的索引。这样,下次执行该行代码的时候,就可以直接执行编译结果了。

WebAssembly的前生今世

但是基线编译器的编译时间不能太长,否则会导致程序执行被卡住,因此基线编译器生成的是一种“未经优化的编译结果”。如果监视器发现某一段代码被执行了很多很多次,那么就会把它标记为“hot”,然后送给 优化编译器(optimizer compiler) 去生成一个更加快速和高效的编译版本出来,虽然慢一点,正所谓磨刀不误砍柴工。

WebAssembly的前生今世 但是,优化编译器的优化需要基于一定的假设:所有变量必须具有相同的类型或者结构。但是Javascript是一种动态类型语言,变量的类型是不确定的,因此当不能满足假设时就会出现 去优化(deoptimization) 现象,需要丢弃优化编译器生成的优化代码,重新回到解释器或者基线编译器。

举个例子:优化编译器可能会假设arr[i]一定是int类型,并为其生成了一个优化编译版本。但是突然执行到sum += ‘a’;的时候就懵圈了,发现之前的类型推断不成立,只好推倒重来。。。

如果一直陷入“优化<–>去优化”的怪圈之中,性能就会大幅下降,甚至还不如直接使用基线编译器。归根到底,是由于Javascript被设计为了一种动态类型语言,编译器无法在编译之前获悉变量的确切类型。如何克服这一“缺陷”呢?

3.性能优化的两大阵营

各大互联网巨头都在为提升Javascript性能进行着不懈地努力,并逐渐形成了两大阵营:

WebAssembly的前生今世 一方以Google / Microsoft / Facebook为首,主张实现Javascript的 超集(superset) ,说白了就是创造一种新语言,在兼容Javascript的基础上增加各种关键字,让开发者显式指定变量类型,从而转变成一种静态类型语言。微软的TypeScript就是一个典型范例,显然,这会增加开发者的学习成本。

另一方Mozilla基金会旗下的Firefox则另辟蹊径,提出了一种Javascript的 子集(subset) ,取名asm.js。这个asm.js只支持两种数据类型:

  • 32位带符号整数
  • 64位带符号浮点数

其他类型比如字符串、对象、布尔值什么的统统作为二进制数据通过TypedArray的形式进行访问。

这两种数据类型是通过一种特殊的标记来表示的,参见下面的例子:

var a = 1;

var x = a | 0;  // x 是32位整数
var y = +a;  // y 是64位浮点数

可以发现,即使Javascript引擎不支持asm.js也没关系,完全不影响执行结果的正确性。这样一来,原先的Javascript程序员不需要学习任何新语法,就可以使用静态类型的语言了。Javascript引擎可以针对asm.js进行优化,根据测评,asm.js的运行效率可以接近native代码的50%!

asm.js的威力还不止于此,有一个叫Emscripten的开源项目,甚至可以把我们所熟悉的各种强类型语言都转换成asm.js!具体来说,Emscripten先使用LLVM的编译器前端把源码编译成LLVM字节码,然后再通过编译器后端把字节码转换成asm.js。也就是说,我们用C/C++或者 Go 写的程序,不需要任何改动,只需要用Emscripten编译一下,就可以在Web端运行!这实在是太激动人心了。

WebAssembly的前生今世

4.WebAssembly闪亮登场

讲了这么多,有人问了,这些跟WebAssembly有什么关系?

上面介绍了asm.js的种种优点,各大互联网巨头自然不会熟视无睹。终于,Google、Microsoft、Apple、Mozilla这四大家族一致觉得asm.js这个方法有前途,干脆坐下来谈一谈,标准化一下,这样大家就都可以用了。

WebAssembly的前生今世

巨头们商量的结果,是推出一个更为激进的方案,并在所有主流浏览器上增加支持,这就是WebAssembly。

WebAssembly的前生今世

既然asm.js已经是静态类型了,那么何不干脆省去Javascript解析过程,直接把它编译成字节码然后送到虚拟机里去执行?不过后来改成了传AST,因为发现AST比字节码更易于压缩,便于网络传输。实验数据表明,这一改动至少可以使性能提升20倍以上!

另外,在支持WebAssembly的浏览器上,还可以通过传统的AOT(Ahead-Of-Time)编译方式把WASM字节码编译成机器代码直接运行,就没解释器或者JIT什么事儿了。当然,并非所有浏览器版本都支持WebAssembly,如果碰到不支持的情况,可以通过一种 polyfill 技术,利用一段Javascript代码把WASM字节码重新转换成asm.js,然后送到解释器或者JIT那边继续执行。

WebAssembly的前生今世

总结一下:WebAssembly是一种可移植的、安全高效的二进制格式标准。它的目标并不是取代Javascript,相反是丰富了Javascript的生态,与Javascript共生共存。它具备以下几个优点:

  • 体积小:这是二进制格式相比文本格式的显著优势,更适合网络传输
  • 性能高:无需经过源码解析和JIT等过程,可以快速转换为机器码运行,没有“去优化”风险
  • 安全性高:无法直接看到源码,即使通过反汇编,获得的代码也难以阅读
  • 兼容性强:有四大主流浏览器厂商背书,兼容性有保障,此外即使浏览器不支持,还可以通过polyfill技术保证程序正常运行

当然,目前阶段WebAssembly还存在着不少问题:

  • 可调试性差:由于是二进制格式,调试代码比较困难
  • 互操作性能差:WebAssembly目前无法操作DOM,也不能操作WebGL等浏览器API,因此这部分工作仍需交由Javascript来完成,存在一定的性能瓶颈,这个问题已经在roadmap中提上了日程
  • 不支持垃圾收集:这意味着开发者需要自己管理内存,增加了额外的开发成本

不过瑕不掩瑜,WebAssembly已经成为W3C标准,又有四大护法加持,相信WebAssembly的未来一片光明。

参考:

https://vimeo.com/96425312

https://juejin.im/post/5a6547d0f265da3e283a1df7

https://blog.csdn.net/cxihu/article/details/79360213

https://medium.com/basecs/a-most-perfect-union-just-in-time-compilers-2938712a9f6a

https://hackernoon.com/webassembly-the-journey-jit-compilers-dfa4081a6ffb

http://rednaxelafx.iteye.com/blog/492667

https://segmentfault.com/a/1190000008632441

https://blog.csdn.net/szengtal/article/details/72614408

https://hackernoon.com/webassembly-the-journey-jit-compilers-dfa4081a6ffb

http://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html

http://asmjs.org/spec/latest/#ahead-of-time-compilation

https://blog.csdn.net/chenqiuge1984/article/details/80131055

更多文章欢迎关注“鑫鑫点灯”专栏: https://blog.csdn.net/turkeycock

或关注飞久微信公众号: WebAssembly的前生今世

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

可伸缩架构

可伸缩架构

【美】Lee Atchison / 张若飞、张现双 / 电子工业出版社 / 2017-7 / 65

随着互联网的发展越来越成熟,流量和数据量飞速增长,许多公司的关键应用程序都面临着伸缩性的问题,系统变得越来越复杂和脆弱,从而导致风险上升、可用性降低。《可伸缩架构:面向增长应用的高可用》是一本实践指南,让IT、DevOps和系统稳定性管理员能够了解到,如何避免应用程序在发展过程中变得缓慢、数据不一致或者彻底不可用等问题。规模增长并不只意味着处理更多的用户,还包括管理更多的风险和保证系统的可用性。作......一起来看看 《可伸缩架构》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换