Node.js的执行速度远超Ruby、Python等脚本语言,这背后都是V8引擎的功劳。本文将介绍如何编写高性能Node.js代码。V8是Chrome背后的JavaScript引擎,因此本文的相关优化经验也适用于基于Chrome浏览器的JavaScript引擎。
V8优化技术概述
V8引擎在虚拟机与语言性能优化上做了很多工作。不过按照Lars Bak的说法,所有这些优化技术都不是他们创造的,只是在前人的基础上做的改进。
隐藏类(Hidden Class)
为了减少JavaScript中访问属性所花的时间,V8采用了和动态查找完全不同的技术实现属性的访问:动态地为对象创建隐藏类。这并不是什么新想法,基于原型的编程语言Self就用map来实现了类似功能。在V8中,当一个新的属性被添加到对象中时,对象所对应的隐藏类会随之改变。
我们用一个简单的JavaScript函数来加以说明:
function Point(x, y) {
this.x = x;
this.y = y;
}
当new Point(x, y)执行时,一个新的Point对象会被创建。如果这是Point对象第一次被创建,V8会为它初始化一个隐藏类,不妨称作C0。因为这个对象还没有定义任何属性,所以这个初始类是一个空类。到此时为止,对象Point的隐藏类是C0(如图1)。
图1 对象Point的隐藏类C0
执行函数Point中的第一条语句会为对象Point创建一个新的属性x。此时,V8会在C0的基础上创建另一个隐藏类C1,并将属性x的信息添加到C1中:这个属性的值会被存储在距Point对象偏移量为0的地方(如图2)。
图2 对象Point的隐藏类被更新为C1
在C0中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性x之后能找到C1作为新的隐藏类。此时对象Point的隐藏类更新为C1。
执行函数Point中的第二条语句会添加一个新的属性y到对象Point中。同理,此时V8会有以下操作。
- 在C1的基础上创建另一个隐藏类C2,并在C2中添加关于属性y的信息:这个属性将被存储在内存中离Point对象的偏移量为1的地方。
- 在C1中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性y之后能找到C2作为新的隐藏类。此时对象Point的隐藏类被更新为C2(如图3)。
图3 对象Point的隐藏类被更新为C2
乍一看似乎每次添加一个属性都创建一个新的隐藏类非常低效。实际上,利用类转移信息,隐藏类可以被重用。下次创建一个Point对象时,就可以直接共享由最初那个Point对象所创建出来的隐藏类。
例如,又有一个Point对象被创建出来,一开始Point对象没有任何属性,它的隐藏类将会被设置为C0。当属性x被添加到对象中时,V8通过C0到C1的类转移信息将对象的隐藏类更新为C1,并直接将x的属性值写入到由C1所指定的位置(偏移量0)。当属性y被添加到对象中时,V8又通过C1到C2的类转移信息将对象的隐藏类更新为C2,并直接将y的属性值写入到由C2所指定的位置(偏移量1)。尽管JavaScript比通常的面向对象编程语言都更加动态一些,然而大部分JavaScript程序都会表现出像上文描述的那样运行时高度结构