V8可以讓JavaScript加速350倍,所以我們有很多優化的空間,在這之前,我們必須了解V8優化JavaScript的原理,然后寫出針對V8的代碼。
下面會使用"Be prepared"這個詞語,單詞的意思是:
- 理解V8優化原理
- 寫出深思熟慮的JavaScript代碼
- 使用工具測試性能,幫助改進
隱藏類
變量類型對生成高速優化的代碼非常有幫助,但是JavaScript確實若類型的。如何才能讓JavaScript跑的像C++一樣快呢?答案就hidden classes
Hidden Classes讓JavaScript更快
- V8在內部為創建隱藏類
- 具有相同隱藏類的對象可以使用相同的優化代碼
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的兩次轉變和三次申請空間。如果不理解可以看視頻。
Paste_Image.png
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)去加速執行
Paste_Image.png
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