V8可以让JavaScript加速350倍,所以我们有很多优化的空间,在这之前,我们必须了解V8优化JavaScript的原理,然后写出针对V8的代码。
下面会使用"Be prepared"这个词语,单词的意思是:
- 理解V8优化原理
- 写出深思熟虑的JavaScript代码
- 使用工具测试性能,帮助改进
隐藏类
变量类型对生成高速优化的代码非常有帮助,但是JavaScript确实若类型的。如何才能让JavaScript跑的像C++一样快呢?答案就hidden classes
Hidden Classes让JavaScript更快
- V8在内部为创建隐藏类
- 具有相同隐藏类的对象可以使用相同的优化代码
如果不理解上面的原理,可以查看V8设计原理或者ppt
tips:使用构造函数初始化数据
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
p2.z = 55// warning! p1 and p2 now have// different hidden classes
当V8解析p2.z = 55
时,p1,p2使用了不同的隐藏类了,就意味着要创建一个新的隐藏类,cache也要重建,所以尽量不要这样。如果你没有用构造函数,请保证对象赋属性的顺序是一样的。
高效的描述值
Be Prepared - Numbers
我们看到下图中,V8使用一个标签来表示不同的对象,很明显对于数字,我们使用能用31位有符号整数是效率最高的。
Prefer numeric values that can be represented as 31-bit signed integers
var i = 42; // this is a 31-bit signed integer
var j = 4.2; // this is a double-precision floating point number
Be Prepared - Arrays
V8有两种处理数组的方式
- Fast Elements: 线性存贮,连续的buffer,性能好
- Dictionary Elements: hash table storage otherwise
避免性能陷阱
- 使用从0开始连续的key
下面明显是字典形式,性能不如Fast Elements
模式
var person = [];
person["firstName"] = "John";
person["lastName"] = "Doe";
person["age"] = 46;
var x = person.length; // person.length will return 0
var y = person[0]; // person[0] will return undefined
-
不要预先建立太大的数组(e.g. > 64K elements),JavaScript不需要像C语言指定数组大小
这样会变成稀疏数组,也会使用字典模式创建
不要删除数据,特别是数值型的数组
这样会导致两种模式的切换,都会很费时不要使用未初始化的数组,或者被删除的元素
a = new Array();
for (var b = 0; b < 10; b++) {
a[0] |= b; // Oh no! 这里a[0]是undefined,v8会做转换,结果是对的,但是更费时间
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
a[0] |= b; // Much better! 2x faster.
}
-
不要导致数组boxing and unboxing
看看下图,既有double,又有其他类型,下面会导致hidden class的两次转变和三次申请空间。如果不理解可以看视频。
var a = [77, 88, 0.5, true]; 这样让解析器一次知道所有信息会更好
总结一下使用数组需要注意的地方:
- 使用
[]
初始化数组 -
小数组可以先指定大小 (<64k) ,因为会使用快速模式
- 不要在数字数组里面使用非数字,数值型的性能已经优化过,包括double
- 如果没有使用数组字面量初始化数组,注意不必要的转换发生
Be Prepared - Full Compiler
V8有两个编译器。你没听错,是编译器,JavaScript是动态语言,一般的动态语言都由解析器解析执行,但是V8可以直接编译成可执行代码。
- "Full" compiler 为所有JavaScript生成可执行代码
- Optimizing compiler 为大多数JavaScript代码生成更优化的代码
"Full" Compiler立刻运行代码
- 生成好的但不是最好的JIT代码,但是支持所有的JavaScript功能
- 编译期间并不假定类型信息,并期待类型在运行时变化
- 运行的时候获取类型并使用Inline Caches (or ICs)去加速执行
Full Compiler Example
this.isPrimeDivisible = function(candidate) {
for (var i = 1; i <= this.prime_count; ++i) {
if (candidate % this.primes[i] == 0) return true;
}
return false;
}
candidate % this.primes[i]
会编译成:
IC的目的是加速处理类型信息,它为JavaScript操作存贮类型相关的代码,当代码运行的时候,它验证所假定的类型信息,然后使用IC去处理。所以,能处理多种类型的操作性能要查一些。
优化tips:
单一的操作比多样的操作好
Monomorphic use of operations is preferred over polymorphic operations
function add(x, y) {
return x + y;
}
add(1, 2); // + in add is monomorphic(所有的操作都是数值类型的话)
add("a", "b"); // + in add becomes polymorphic
Optimizing compiler
优化编译实际上使用inline技术,还记得在C++中的inline吗,是一个意思。短小的函数,并且经常调用的函数,会被编译器优化成inline。一般单一类型的函数和构造函数会被inline。
我们看一下代码(**inline可以避免跳转
**):
一些有用的命令
d8 --trace-opt primes.js //log names of optimized functions to stdout
d8 --trace-bailout primes.js //找到被try catch包住不能优化的函数
d8 --trace-deopt primes.js //v8必须取消优化的代码,找到以后可以修改
给chrome加启动参数
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \--js-flags="--trace-opt --trace-deopt --trace-bailout"
prime.js是一个测试获得质数的函数,ppt的作者用来测试性能用的。
function Primes() {
this.prime_count = 0;
this.primes = new Array(25000);
this.getPrimeCount = function() { return this.prime_count; }
this.getPrime = function(i) { return this.primes[i]; }
this.addPrime = function(i) {
this.primes[this.prime_count++] = i;
}
this.isPrimeDivisible = function(candidate) {
for (var i = 1; i <= this.prime_count; ++i) {
if ((candidate % this.primes[i]) == 0) return true;
}
return false;
}
};
function main() {
p = new Primes();
var c = 1;
while (p.getPrimeCount() < 25000) {
if (!p.isPrimeDivisible(c)) {
p.addPrime(c);
}
c++;
}
print(p.getPrime(p.getPrimeCount()-1));
}
main();
你需要编译v8,获得d8命令行,在windows在编译可以参考这篇文章使用visual studio编译v8
这些命令跑出来的结果还看不太懂,等以后仔细研究在来分享
本文翻译自这个ppt,可以观看youtube演讲
这篇文章也很好Performance Tips for JavaScript in V8