内容简介:上帝花了7天时间创造了世界,Brendan Eich大神在1995年花了10天时间创造了Javascript。Javascript的初衷是面向非专业编程人员和设计师的,因此大神认为不应该让这些不专业的人理解编译器这么专业的概念。这两个概念其实不太严谨,应该改为“
1.先从Javascript说起
上帝花了7天时间创造了世界,Brendan Eich大神在1995年花了10天时间创造了Javascript。
为什么Javascript被设计为解释型语言?
Javascript的初衷是面向非专业编程人员和设计师的,因此大神认为不应该让这些不专业的人理解编译器这么专业的概念。
解释型语言 vs. 编译型语言
这两个概念其实不太严谨,应该改为“ 主流实现为解释器的语言 ”和“ 主流实现为编译器的语言 ”更为妥当。事实上很多语言采用的是混合型实现,比如 Java 需要先编译成字节码,而虚拟机又是解释执行的,执行过程中又会用到JIT即时编译,因此很难说它到底属于哪一种类型的语言。实际上,编译型和解释型最大的区别在于是否保存和复用生成的目标代码(不管是存在磁盘还是内存),编译型会,而解释型则是逐条执行,用完就扔。因此,这里主要讨论编译器和解释器。
编译器的一般实现参见下图:
现代编译器一般可以分为前端和后端:前端主要负责词法分析、语法分析、语义检查等工作,并输出中间代码(为了跨平台)。而后端则负责根据把中间代码转换为目标机器代码,并进行代码优化。所以,编译器的输入是所有代码,最终输出的是目标代码,仅需编译一次,后面就可以多次运行。
解释器则略有不同,一般实现参见下图:
早期的解释器只需要经过词法分析和语法分析,生成抽象语法树AST,就可以送入“树遍历型解释器”执行并输出最终结果了。( esprima 网站上可以查看生成的AST结果)
但是后来大家发现,直接解释执行AST这种树形结构效率比较低,如果把AST转换成一种线性结构再执行效果会更好,这就是字节码。所以简单来说, 字节码就是AST的后序遍历结果 ,而执行字节码的程序则被称为虚拟机(VM)。
虚拟机的实现多种多样,最常规的做法就是一行一行地解释执行,但是如果遇到循环的话效率就比较低了,需要一遍又一遍地解释执行。当然,你也可以先编译再执行,但是由于编译优化比较耗时,如果某段代码只需要执行一遍的话,就又得不偿失了。有没有什么办法能结合这两种方式的优点呢?还真有,这就是JIT。
2.JIT(Just-In-Time)编译器
那么这个JIT编译器是怎么工作的呢?其实很简单,就是计数器 + 缓存。
刚开始,还是一行一行地解释执行,但是JIT里包含了一个 监视器(profiler) ,用于监视每行代码执行的次数。如果某一行代码执行了好几次,那么监视器就会把这行代码标记为“warm”,并送给 基线编译器(baseline compiler) 去编译。基线编译器会把该行代码编译成一个 桩(Stub) ,并分配一个“行号 + 变量类型”的索引。这样,下次执行该行代码的时候,就可以直接执行编译结果了。
但是基线编译器的编译时间不能太长,否则会导致程序执行被卡住,因此基线编译器生成的是一种“未经优化的编译结果”。如果监视器发现某一段代码被执行了很多很多次,那么就会把它标记为“hot”,然后送给 优化编译器(optimizer compiler) 去生成一个更加快速和高效的编译版本出来,虽然慢一点,正所谓磨刀不误砍柴工。
但是,优化编译器的优化需要基于一定的假设:所有变量必须具有相同的类型或者结构。但是Javascript是一种动态类型语言,变量的类型是不确定的,因此当不能满足假设时就会出现 去优化(deoptimization) 现象,需要丢弃优化编译器生成的优化代码,重新回到解释器或者基线编译器。举个例子:优化编译器可能会假设arr[i]一定是int类型,并为其生成了一个优化编译版本。但是突然执行到sum += ‘a’;的时候就懵圈了,发现之前的类型推断不成立,只好推倒重来。。。
如果一直陷入“优化<–>去优化”的怪圈之中,性能就会大幅下降,甚至还不如直接使用基线编译器。归根到底,是由于Javascript被设计为了一种动态类型语言,编译器无法在编译之前获悉变量的确切类型。如何克服这一“缺陷”呢?
3.性能优化的两大阵营
各大互联网巨头都在为提升Javascript性能进行着不懈地努力,并逐渐形成了两大阵营:
一方以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端运行!这实在是太激动人心了。
4.WebAssembly闪亮登场
讲了这么多,有人问了,这些跟WebAssembly有什么关系?
上面介绍了asm.js的种种优点,各大互联网巨头自然不会熟视无睹。终于,Google、Microsoft、Apple、Mozilla这四大家族一致觉得asm.js这个方法有前途,干脆坐下来谈一谈,标准化一下,这样大家就都可以用了。
巨头们商量的结果,是推出一个更为激进的方案,并在所有主流浏览器上增加支持,这就是WebAssembly。
既然asm.js已经是静态类型了,那么何不干脆省去Javascript解析过程,直接把它编译成字节码然后送到虚拟机里去执行?不过后来改成了传AST,因为发现AST比字节码更易于压缩,便于网络传输。实验数据表明,这一改动至少可以使性能提升20倍以上!
另外,在支持WebAssembly的浏览器上,还可以通过传统的AOT(Ahead-Of-Time)编译方式把WASM字节码编译成机器代码直接运行,就没解释器或者JIT什么事儿了。当然,并非所有浏览器版本都支持WebAssembly,如果碰到不支持的情况,可以通过一种 polyfill 技术,利用一段Javascript代码把WASM字节码重新转换成asm.js,然后送到解释器或者JIT那边继续执行。
总结一下:WebAssembly是一种可移植的、安全高效的二进制格式标准。它的目标并不是取代Javascript,相反是丰富了Javascript的生态,与Javascript共生共存。它具备以下几个优点:
- 体积小:这是二进制格式相比文本格式的显著优势,更适合网络传输
- 性能高:无需经过源码解析和JIT等过程,可以快速转换为机器码运行,没有“去优化”风险
- 安全性高:无法直接看到源码,即使通过反汇编,获得的代码也难以阅读
- 兼容性强:有四大主流浏览器厂商背书,兼容性有保障,此外即使浏览器不支持,还可以通过polyfill技术保证程序正常运行
当然,目前阶段WebAssembly还存在着不少问题:
- 可调试性差:由于是二进制格式,调试代码比较困难
- 互操作性能差:WebAssembly目前无法操作DOM,也不能操作WebGL等浏览器API,因此这部分工作仍需交由Javascript来完成,存在一定的性能瓶颈,这个问题已经在roadmap中提上了日程
- 不支持垃圾收集:这意味着开发者需要自己管理内存,增加了额外的开发成本
不过瑕不掩瑜,WebAssembly已经成为W3C标准,又有四大护法加持,相信WebAssembly的未来一片光明。
参考:
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
或关注飞久微信公众号:以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。