inside the V8 engine + 5 tips on how to write optimized code
Foreword 前言
两周前我们开始了js深入了解以及它如何的工作原理的研究:我们认为通过了解js的代码构建和他们的运行机制可以写出更好的代码和app。
前面的博文主要是js引擎概述,运行和堆栈。第二篇会深入谷歌V8的js引擎部分,我们也会提供一点点关于如何编写更好的js代码的建议-最好
Overview 概览
js引擎是一个程序或者执行js代码解析器。js引擎可以实现为一个标准的翻译,或者以某种形式将js代码即时编译成字节码。 下面是流行的js执行引擎清单列表:
- v8 - 开源,由google开发,用C++ 编写
- Rhino — 由火狐管理,开源,完全用java开发
- SpiderMonkey —
- JavaScriptCore —
- KJS —
- Chakra —
- Chakra —
- Nashorn —
- JerryScript —
为何创造v8引擎
由google开发V8引擎是开源的,用c++编写。这个引擎用于Google Chrome。不像其他的引擎,当前流行的node.js也是基于V8运行。
v8有两个编译器
在V8的5.9版本出现之前,v8使用两个编译器:
- full-codegen -- 一个简单快速的编译器,生成编译简单和相对慢的机器码。
- Crankshaft -- 一个更复杂的(即时)优化编译器,生成高度优化的代码。
v8引擎内部也是使用多线程的:
- 主线程做你想要的工作:获取代码,编译代码然后执行。
- 还有一个单独的线程编译,所以主线成可以继续执行代码,而前者是优化代码
- 在运行时有一个分析器的线程会告诉我们哪些方法花费了大量的事件,这样Crankshaf可以去优化它
- 一些线程用来处理垃圾回收扫描
当开始执行js代码,v8使用full-codegen直接解析js翻译成机器码,没有任何转换。这允许它开始执行机器代码非常快。注意v8不使用中间字节码表示这种方式不需要翻译。
当你的代码运行一段时间后,解析器线程会聚集足够的数据告诉我们哪些方法必须优化。 接下来,Crankshaft开始在另一条线程上优化。它将js抽象语法树(syntax tree )转换成一个叫Hydrogen 的高级静态单元分配表示 (SSA) ,而且尝试去优化 Hydrogen这个图。大多数的优化是在这级完成的。
Inlining 代码嵌入
首次优化尽可能多的提前嵌入代码。代码嵌入是将使用函数的地方(调用函数的那一行)替换成被调用函数的本体。这个简单的步骤可以使接下来的优化更有意义。
Hidden class 隐藏类
js是一个基于原型链的语言:它没有类和对象是通过克隆产生的。js也是一个动态编程语言,这意味着在对象实力化以后可以很简单增加或者移除属性。 大多数js解析器使用类似字典一样的结构(基于散列函数) 去储存对象属性值在内存中的地址。这种构造使js在检索对象属性值上比其他类似java,c++等动态语言,花费更大的计算量。在java中,所有的对象属性在编译之前都已经被固定的对象容器决定。而且在运行时不可以动态的添加或者删除属性。(c++的动态类型是另一个话题)。结果,每一个属性的值能够以连续的buffer储存在内存中,在每个属性值之间有一个偏移量。这个偏移量的长度很容易根据属性类型决定。然而在js中这是不可能的,因为js的属性值可以在运行时改变。 因为使用字典去查找对象属性在内存中的地址的效率很低,V8使用了一个不同的方法代替:隐藏类。隐藏类与在java等语言中使用的固定对象布局的工作方式类似,除了他在运行时被创建。来让我们看一下真实的代码:
function Point(x,y){
this.x = x;
this.y = y;
}
var p1 = new Point(1,2);
复制代码
一旦 new Point(1,2) 被调用,V8会创建一个名为 C0 的隐藏类。