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

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

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

原生函數(native function)是 JavaScript 語言的一部分,這些函數有別於開發者編寫的自定義函數。當咱們在 profiler 中查看代碼的調用棧時,這些函數是被過濾掉的。咱們在 profiler 中看到的只有本身寫的代碼。前端

當咱們捕獲調用棧時,Chrome 並不會捕獲 C++ 寫的函數。不過,在 V8 引擎中不少 javascript 原生函數都是使用 javascript 語言編寫的java

V8 使用 JavaScript 自己實現了 JavaScript 語言的大部份內置對象和函數。 例如,promise 功能就是經過 JavaScript 編寫的。咱們把這樣的內置函數稱爲自主託管(self-hosted)。程序員

若是咱們開啓 「Show native functions」 設置,Chrome 將會在 profiler 中顯示這些函數。web

Chrome 分析器是如何工做的

爲了找到那些耗時最多的代碼,Chrome 分析器每 100μs 捕獲一個堆棧跟蹤。chrome

這意味着,若是一個函數只須要 50μs 的執行時間,就可能不會在分析器中顯示出來!segmentfault

當你分析幾毫秒以上的時間時,能夠準確瞭解應用程序在什麼時候花費最多的時間。 可是,當你放大 profiler 面板想看更精準的時間時,信息會變得不太準確。數組

分析器也會不一致。 每次運行時,會產生一個稍微不一樣的結果。 有時可能會記錄很是短的函數調用,而在其餘時間再次運行這些函數調用信息可能會丟失。promise

經過這篇博客文章我將爲你們演示如何捕獲並分析原生函數的性能。當你本身運行代碼時,結果可能會有所不一樣。

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 + arr

console.profileEnd("arr + arr")複製代碼

您一般不會在兩個數組上執行加操做。可是因爲某種緣由,我最近看過的一些代碼就是這樣作的。

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

當咱們開啓了查看原生函數功能時,Chrome 調用了 arraytoString 方法,而後調用了 join 方法。

Error().stack

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

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

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

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

不許確的地方

在個人文章的開頭章節,我提到了很是小的時間間隔可能形成結果的不許確。爲了說明這一點,我在另外一臺不一樣配置的電腦上運行了 Error().stack 的代碼段。

咱們看到了 FormatErrorString 函數,而在以前的分析中,這個函數並無顯示出來。

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

相關閱讀

若是對 V8 引擎幹興趣,我在 4月15(星期六)晚 8 點和你們一塊兒聊聊 V8 引擎:前端程序員應該懂點 V8 知識 - SegmentFault 講堂

相關文章
相關標籤/搜索