WebAssembly的前生今世

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

内容简介:上帝花了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的前生今世

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

查看所有标签

猜你喜欢:

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

Django 1.0 Template Development

Django 1.0 Template Development

Scott Newman / Packt / 2008 / 24.99

Django is a high-level Python web application framework designed to support the rapid development of dynamic websites, web applications, and web services. Getting the most out of its template system a......一起来看看 《Django 1.0 Template Development》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具