Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

justjavac發表於2017-03-29

本文翻譯自 Chrome DevTools: Show native functions in JS Profile,中文版首發在我的知乎專欄 V8 原始碼及周邊

在 Chrome DevTools 中可以使用 profiler 檢視原生函式的執行效能:

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

原生函式(native function)是 JavaScript 語言的一部分,這些函式有別於開發者編寫的自定義函式。當我們在 profiler 中檢視程式碼的呼叫棧時,這些函式是被過濾掉的。我們在 profiler 中看到的只有自己寫的程式碼。

當我們捕獲呼叫棧時,Chrome 並不會捕獲 C++ 寫的函式。不過,在 V8 引擎中很多 javascript 原生函式都是使用 javascript 語言編寫的

V8 使用 JavaScript 本身實現了 JavaScript 語言的大部分內建物件和函式。 例如,promise 功能就是通過 JavaScript 編寫的。我們把這樣的內建函式稱為自主託管(self-hosted)。

如果我們開啟 “Show native functions” 設定,Chrome 將會在 profiler 中顯示這些函式。

Chrome 分析器是如何工作的

為了找到那些耗時最多的程式碼,Chrome 分析器每 100μs 捕獲一個堆疊跟蹤。

這意味著,如果一個函式只需要 50μs 的執行時間,就可能不會在分析器中顯示出來!

當你分析幾毫秒以上的時間時,可以準確瞭解應用程式在何時花費最多的時間。 但是,當你放大 profiler 皮膚想看更精準的時間時,資訊會變得不太準確。

分析器也會不一致。 每次執行時,會產生一個稍微不同的結果。 有時可能會記錄非常短的函式呼叫,而在其他時間再次執行這些函式呼叫資訊可能會丟失。

通過這篇部落格文章我將為大家演示如何捕獲並分析原生函式的效能。當你自己執行程式碼時,結果可能會有所不同。

Array.join

首先,我們執行如下程式碼:

var arr = []
for (var i=0; i<1000; i++){
    arr.push(i)
}
console.profile("Array.join")
arr.join(",")
console.profileEnd("Array.join")複製程式碼

選擇 profiler 的 “Chart” 檢視:

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

第一次分析時,我們不選中 “Show native functions”:

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

我們再次執行時,把 “Show native functions” 啟用:

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

當我們把滑鼠指向函式時,會看到更加詳細的資訊:

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

如上資訊中,chrome devtools 展示了原生函式的行號,你可以在 Chrome code search中找到這個檔案 “array.js”。行號資訊可能不同,因為 V8 原始碼的最新版本和 Chrome 使用的 V8 版本可能不一樣。

你可以看到 ArrayJoin 函式在內部呼叫了 InnerArrayJoin

function ArrayJoin(separator) {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.join");

  var array = TO_OBJECT(this);
  var length = TO_LENGTH(array.length);

  return InnerArrayJoin(separator, array, length);
}複製程式碼

InnerArrayJoin 在內部呼叫了 DoJoin

DoJoin 呼叫了 %StringBuilderJoin

%StringBuilderJoin 是使用 C++ 實現的。

稀疏陣列

我們有點偏離主題,但是我認為 V8 處理稀疏陣列(new Array(n))是非常有趣的。

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

為什麼這很有用呢?

下面的程式碼是如何執行的?

arr = new Array(10000000)
for (var i=0; i<10000; i++){
    arr.push(i)
}
console.profile("arr + arr")
arr + arr

console.profileEnd("arr + arr")複製程式碼

您通常不會在兩個陣列上執行加操作。但是由於某種原因,我最近看過的一些程式碼就是這樣做的。

當不是用檢視原生函式時,我們看到了一個匿名函式的呼叫。

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

當我們開啟了檢視原生函式功能時,Chrome 呼叫了 arraytoString 方法,然後呼叫了 join 方法。

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

Error().stack

我們來看一個不同的例子。在 JavaScript 中,您可以使用 Error().stack 獲取當前正在執行的函式的堆疊跟蹤(stack trace)。

當我們執行該程式碼時,一共做了兩件事: 首先我們建立一個新的 Error 物件,然後訪問它的 stack 屬性。

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

獲取堆疊跟蹤的字串描述資訊時,耗費了大量的時間。

我能夠通過獲取一個 Error 物件來加快我正在處理的程式碼:只有當我需要顯示堆疊跟蹤時,才解析其 stack 屬性。

不準確的地方

在我的文章的開頭章節,我提到了非常小的時間間隔可能造成結果的不準確。為了說明這一點,我在另一臺不同配置的電腦上執行了 Error().stack 的程式碼段。

我們看到了 FormatErrorString 函式,而在之前的分析中,這個函式並沒有顯示出來。

Chrome DevTools:在 Profile 效能分析中顯示原生 javascript 函式

(這次的總執行時間是 ~1ms,這意味著 Chrome 需要 10 個呼叫堆疊的樣本。上面的例子花了 ~10ms,因為我在迴圈中呼叫了 10 次 Error().stack。)

相關閱讀

如果對 V8 引擎幹興趣,我在 4月15(星期六)晚 8 點和大家一起聊聊 V8 引擎:前端程式設計師應該懂點 V8 知識 - SegmentFault 講堂

相關文章