1.针对上下文的Snapshot技术
什么是上下文(Contexts)?实际是JS应用程序的运行环境,避免应用程序的修改相互影响,例如一个页面js修改内置对象方法toString,不应该影响到另外页面。chrome浏览器每个process只有一个V8引擎实例,浏览器中的每个窗口、iframe都对应一个上下文。
V8启动时(在执行client js前),需要对全局上下文(第一个context)初始化,读取和解析自实现的内置JS代码(另一种技术,第2点),建立起function、array、string等内置对象及方法(参见bootstrapper中的Genesis类);后续context的创建只需要创建内置对象即可;为了减少全局上下文创建时的CPU、内存消耗,V8使用了Snapshot技术(参见v8_mksnapshot工程),(1)全局上下文初始化后,将目前堆内存序列化为字节代码,保存至磁盘文件;这个过程最重要的是空间地址和对象保存,具体操作(参见serialize文件)是:模拟线性内存空间的分配,遍历堆内存中的所有JS对象,并在模拟空间分配内存(实际是记录对象相对偏移地址),然后序列化对象大小、偏移地址、子对象、对象内容;最后是序列化global handler和stack上的context;(2)加载时,将该snapshot文件反序列化进内存,避免第一个上下文初始化,从而加快V8的启动。
2. Built-in的js代码
(1)利用JS自表达内置对象、方法,如上面代码实现Math.min方法,从而V8在实现代码转译时只需注重基本操作,以%符号开头的函数来自V8运行时函数(参见runtime和codegen);
(2)如何把js嵌入到V8中?利用python工具,将JS文件代码转为const char[]变量(字节代码),存到natives.cc;在执行期间首次访问相关方法时,取相关js代码进行lazy-compile。
(题外话:让面试者实现JS的一些常用函数,也是考察其基本功方法之一。)
3. 建立AST(Abstract SyntaxTree)时内存的管理
V8在建立AST后,对其进行汇编生成动态机器语言,所以AST在code generated后需要回收;针对AST建立过程中多结点内存申请和一次性回收的特点,V8使用了内存段链表管理,并结合scopelock模式,实现少数申请(Segment,8KB~1MB)、多次分配AST结点、一次回收各个Segment的管理方式,既能避免内存碎片,又可以避免遍历AST结点逐个回收内存。
4. CompileCache避免相同代码重复编译
对于一段JS代码,在开始进行词法分析前,会从编译缓存区CompilationCache查找该段代码是否已经被编译过,如果是,则直接取出编译过的机器代码,并返回,这样降低CPU的使用率,换来内存空间一定的占用;如果一个页面中重复加载JS文件,这方法的提速是很明显的;这种做法应该有平衡对比过。
下面先来看下这个缓存区的具体实现:
(1)数据结构采用CompilationCacheTableàHashTable,它为三种不同的JS语句类型(普通script,Eval语句,RegExp正则表达式),并使用了不太雅观的全局变量记录各种table。用三种table记录的原因:相同的script和Eval语句,编译出来的机器代码是不同的(来自annotation),因为Eval语句和执行期的上下文相关,所以缓存的时候还需要加上context作为key,所以三种类型生成的key方式不一样。一开始table数组的各个值为空,直到有编译代码需要加入时才生成hashtable。
(2)Key的生成,需要保证相同语句生成的key是稳定的:
a)普通script语句,使用StringHasher生成key,并保存到String对象中(因为该key不只是用到该处);生成规则是:当语句长度超过16K时,将直接使用它的长度作为key(经过偏移变为32位长度并确保为奇数),否则使用Bob Jenkins hash算法逐个读取语句的每个字符,计算组成一个uint的key;
b)Eval语句,同样使用到StringHasher,在这基础上结合上下文的share info(同样是语句hash,再加上语句的位置信息,详见StringSharedHashHelper)来更新hash值;
c)RegExp语句,同样使用到StringHasher,在这基础上结合RegExp的flag信息(i/g等)来更新hash值(详见RegExpObjectHash),具体见RegExpKey。
这几种key都派生自HashTableKey,后者定义了些公共接口,供hashtable统一使用。
(3)查找方式:使用hash算法,并用线性侦测来解决key的冲突,这是由HashTable继承自FixedArray的特性决定。
5. 属性的快速访问
C++、Java等语言有着类的概念,且属性、方法和类绑定在一起,访问时可根据对象地址+位移快速获得;而JS对象并没有类概念,它实际为hash map,属性可以动态增加、删除,而且在执行时才能获知对象类型。
V8没有像其它JS Engine使用词典结构或红黑树实现的map来管理属性,而是在每个对象附加一个指针,指向hidden class(如果第一次创建该类型对象,则新建hidden class);当对象每添加一个属性时,将新建一个class(记录了每个属性的位移/位置),而原来的class指向新class,即建立起一个hidden class的转换链表。
6. Heap堆内存管理
详见:
http://hi.baidu.com/hycjk/blog/item/20c9ecf87d3d1004d8f9fd6e.html
http://hi.baidu.com/hycjk/blog/item/86b2bf0e000d34ec37d1221e.html
7. Inline caching减少函数调用开销
详见http://en.wikipedia.org/wiki/Inline_caching。JS函数绑定发生在运行时,所以无法通过method tables定位函数入口;通过该技术可以记录函数入口,避免重复查找。
8. 一次性编译生成机器语言
一般JS engine会在AST生成后,将之编译为中间语言(bytecode),在执行时候再解析这些bytecode;Java 也同样编译为这些bytecode,再采用VM(实现跨平台)作为解释器,为了提高效能,Java采用混杂方式,把无关平台、常用的代码编译为机器代码。V8则是一次性把AST编译为机器语言。从assembler相关文件头的Copyright可以看出,这些不同平台(ia32, arm)下的编译器,原型来自Sun Microsystems。
附:
一、一些JS engine设计的考虑点:
1、 快速——解析、建构语法树、执行等多个方面,例如属性访问,避免字典查找;优化代码,编译生成动态机器码,而非按语句解释执行,且机器码可以存放cache后重复执行;
2、 小巧——占用内存低,分配回收内存及时和有效;
3、 安全——运行上下文切换和检查;
4、 容错性高
5、 易于与浏览器集成;
6、 跨平台;
7、 提供api接口;
8、 支持调试;
9、 其它细节:正则表达式的解析和实现、hash对象的实现(大多此类对象size较小),需要考虑JS本身特性和目的。
二、一些JS语言特性
1、对象为hash map,属性可以随时添加、修改、删除,对象类型在执行时才可知;
2、prototyp chain在执行时可以修改
3、eval能改变执行上下文
4、with动态添加对象到scope chain(作用域)
三、词法分析器
四、V8引用的第三方代码:
1、正则引擎:刚开始使用webkit项目中的JSCRE(基于剑桥大学Philip Hazel开发的,被广泛使用的PCRE库),后来替换为全新的Irregexp(Automata理论);
2、dtoa,double转为字符串,David M. Gay,under an MIT license;
3、Strongtalk assembler,是个以 C++ 封裝的 JIT (Just-In-Time) Assemble。
五、一些资料地址
http://code.google.com/intl/zh-CN/apis/v8/design.html
http://code.google.com/intl/zh-CN/apis/v8/embed.html
http://www.docin.com/p-46635034.html
http://www.greenpublishers.com/neat/200901/3coverstory.pdf
http://www.cnblogs.com/RicCC/archive/2008/02/15/javascript-object-model-execution-model.html
http://www.cnblogs.com/duguguiyu/archive/2008/10/02/1303095.html tagged pointer?
http://hllvm.group.javaeye.com/group/topic/17840