Chrome DevTools: 在 Profile 性能分析中顯示原生 JS 函數

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

在 Chrome DevTools 中可以使用 profiler 查看原生函數的執行性能:

原生函數(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」 視圖:

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

我們再次運行時,把 「Show native functions」 啟用:

當我們把滑鼠指向函數時,會看到更加詳細的信息:

如上信息中,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))是非常有趣的。

為什麼這很有用呢?

下面的代碼是如何運行的?

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

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

當不是用查看原生函數時,我們看到了一個匿名函數的調用。

當我們開啟了查看原生函數功能時,Chrome 調用了 array 的 toString 方法,然後調用了 join 方法。

Error().stack

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

當我們運行該代碼時,一共做了兩件事: 首先我們創建一個新的 Error 對象,然後訪問它的 stack 屬性。

獲取堆棧跟蹤的字元串描述信息時,耗費了大量的時間。

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

不準確的地方

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

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

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


推薦閱讀:

Runtime, Engine, VM 的區別是什麼?
在不使用node的情況下,開發者怎樣在js里調用一個自己實現的c/c++函數?
新手應該如何讀Google V8引擎源代碼?
js中為什麼沒有自乘自除?只有自加自減,為什麼?
如何評價Microsoft的開源項目napajs?

TAG:GoogleChrome | V8 | 数组 |