|
自从十九世纪九十年底中,Netscape 浏览器集成了JavaScript,它使得 web 开发者更加容易访问 HTML 页面元素如:表单、frames 和图象。JavaScript 迅速流行,用于定制控件和增加动画效果。到19世纪九十年代后,出现大量的用于切换图片以响应用户生成的鼠标事件的脚本。
最近,随着 AJAX 的出现,JavaScript 已经称为了实现基于 web 的应用(如:Gmail)的中心技术。JavaScript 程序由简单的几行成长为几百k的源码。然而 JavaScript 是被设计成实现 web 应用的非常有效的技术。性能已经成为开发基于 web 的 JavaScript 应用的限制因素。
V8 是全新的 JavaScript 引擎,主要设计目标是快速执行大量 JavaScript 脚本应用。 在几种 benchmark 测试中,V8 的性能是 JScript(IE内的引擎)、SpiderMonkey(Firefox所用)和 JavaScriptCore(safari 所用)的许多倍。如果您的 web应用受限于 JavaScript 的执行速度,则使用V8 代替您当前的 JavaScript 引擎将很可能提高您的应用的性能。性能提升的程度依赖于JavaScript 的多少和 JavaScript 的特点。例如,如果在您的 应用中函数倾向于一次一次被执行,则与仅执行一次许多不同函数相比性能将大大地提升。当您阅读完本文档时,您将更加清楚性能提升的原因。
V8性能的3个关键方面:
快速属性访问
动态生成机器码
高效的垃圾回收
快速属性访问
JavaScript 是动态的编程语言:对象的属性可以增加和删。这意味着一个对象的属性可能改变。大多数 JavaScript 引擎使用类似字典的数据结构存储对象的属性,每 个属性的访问需要动态查找定位属性在内存的位置。这种典型的访问属性方法比在 Java 和 Smalltalk 中访问实例化变量慢得多。在这些语言中,实例化 变量通过由编译器决定的根据对象类型定义的对象固定的布局定义的固定的偏移来定位。加载或存储访问非常简单,通常仅仅需要一条简单点的指令。
为了减少访问 JavaScript 属性的时间,V8 没有使用动态查找访问属性,取而代之的是 V8 动态创建后台隐藏的类。这个想法不是最新 才有的-是基于原型的编程语言自身的特性(相似地用于映射某些东西)(见 An Efficient Implementation of Self, a Dynamiclly-Typed Object-Oriented Language Based on Prototypes)。在 V8 中,当一个新的属性增加时,对象改变它的隐藏类。
为了更加清除说明这一个点,想象如下一个的简单的 JavaScrip t函数:
function Point(x, y) {
this.x = x;
this.y = y;
}
当 new Point(x, y) 被执行时一个新的 Point 对象被创建。当 V8 首次创建时,V8 创建一个初始的隐藏类 Point,例子中称为 C0。如果对象初始时没有任何属性则定义 空的初始类。此处 Point 对象的隐藏的类是 C0 。
v8_map_trans_a
执行在 Point 里第一个语句 ( this.x = x; )则在Point对象中创建一个新的属性x, 这种情况下 V8:
基于 C0 创建另外一个的隐藏类 C1, 然后增加描述有属性 x 的信息给 C1,这个属性的值存在 Point 对象偏移为0的位置。
如果一个 x 属性添加到 C0 描述的对象上那么隐藏类 C1 应该取代 C0,同时更新 C0 以表示前面的过渡。此时 Point 对象的隐藏类是 C1。
v8_map_trans_b
执行Point的第二个语句( this.y = y; ),则在Point对象中创建一个新的属性y,这种情况下 V8:
基于 C1 创建另外一个隐藏类 C2,然后添加描述属性 y 的信息给 C2,同时属性值在 Point 对象的偏移为 1。
如果一个 y 属性添加到 C1 描述的对象上那么隐藏类 C2 应该取代 C1,同时更新 C1 以表示前面的过渡。此时 Point 对象的隐藏类是 C2。
v8_map_trans_c
无论何时增加属性,以上似乎通过创建一个隐藏类不是很高效。然而由于类的过渡,隐藏类可以重用,实际的效率较高。第二次创建一个新的Point时是不需要创建新的隐藏类,相反新的 Point 对象共享了第一个 Point 对象的类型。例如,如果创建另外一个Point对象:
初始的 Point 对象没有属性,因此最新建的对象引用初始类 C0。
当增加属性 x,V8 遵循隐藏类从 C0 到 C1 过渡。并根据 C1 中 x 的偏移写入 x 的值。
当增加属性 y,V8 遵循隐藏类从 C1 到 C2 过渡。并根据 C2 中 y 的偏移写入 y 的值。
尽管 JavaScript 比通常的面向对象的语言更加动态,使用上面方法通常的 JavaScript 程序的运行时行为将导致高度的结构贡献。这里列 举使用隐藏类的两个优点:属性访问不需要字典查找,同时使得 V8 能使用面向对象的优化,内联缓存。更多的内联缓存见 Efficent Implementation of the Smalltalk-80 System。
动态生成机器指令
首次执行时,V8 直接将 JavaScript 源码编译成机器码。不存在中间过程的字节码,没有解释器。访问属性通过处理内联的缓存代码,这些代码可以像 V8 执行时一样转为的其他机器指令。
在首次访问一个给定对象的属性时,V8 生成了对象当前的隐藏类。V8 使用隐藏类内部生成内联缓存信息并通过预测这个类是否将用于在同一节代 码的所有将来的对象来优化属性的访问。如果 V8 成功预测则属性的值将用一个简单的操作读取或者写入。如果预测不正确,则 V8 将删除被优化的代码。
例如,JavaScript 代码访问 Point 对象的属性x:
point.x
在 V8 中,访问 x 的机器码是:
# ebx = the point object
cmp [ebx,<hidden class offset>],<cached hidden class>
jne <inline cache miss>
mov eax,[ebx, <cached x offset>]
如果对象的隐藏类不匹配缓存的隐藏类,则执行跳转到 V8 运行时系统处理内嵌缓存缺失同时生成内嵌缓存代码,通常遇到的情况是匹配,则简单地返回属性 x 的值。
当有许多对象具有相同的隐藏类时,则就像大多数静态语言一样这些对象都受益。使用隐藏类访问属性和内嵌缓存与机器码生成优化组合在一起,对于相同类型的对象以相似的方式频繁创建和访问,这将大大地提高执行大多数 JavaScript 代码的速度。
高效的垃圾回收
V8 回收那些在过程中不再需要的对象的内存,这一个过程称为垃圾回收。为了确保快速的对象分配,垃圾回收时间足够短暂,没有内存碎片,V8 使用了 stop-the-world,分代,精确垃圾回收器。这意味着 V8:
在执行垃圾回收期间停止程序的执行。
大多数的垃圾回收都是在处理一部分对象的堆。则最小化对于停止应用执行的影响。
总是精确知道何处的所有对象和指针在内存中。避免了错误的指示对象的指针可能带来的内存泄漏。
在 V8 中,对象的堆分成两段:刚创建对象的新空间和在垃圾回收时仍在使用的老对象。如果一个对像被垃圾回收器回收,V8 更新所有指向这个对象的指针。
附:英文原文
|
|