從爬樓梯簡單談談執行性能

爬樓梯這個問題,其實思路是很明確的:爬到當前樓梯的方法數 = 爬到上一級樓梯(然後再爬一級,只有這一種選擇)的方法數 + 爬到上兩級樓梯(然後再爬兩級,只有這一種選擇)的方法數,也就是:
f(x)=f(x1)+f(x2) f(x)=f(x-1)+f(x-2)
這個其實是老生常談了,它其實就是著名的斐波那契數列,可以循環或者遞歸求解,也可以用dp來剪枝,然後進一步對dp的數組進行優化(所謂的滾動數組),乃至矩陣快速冪、通項公式,或者更極端的直接打表(如果指定範圍的話),就不一一解釋了。

這裏舉個求通項公式的例子。我們可以看到這樣一段代碼:

function climbStairs(n: number): number {
  return (
    (((1 + Math.sqrt(5)) / 2) ** (n + 1) -
      ((1 - Math.sqrt(5)) / 2) ** (n + 1)) /
    Math.sqrt(5)
  );
}

可以看到,這段代碼裏有大量的重複計算,比如重複了三次的Math.sqrt(5)。但是,這段代碼的執行結果,有時候卻比不重複計算的快:

function climbStairs(n: number): number {
  const sqrt5 = Math.sqrt(5);
  return (((1 + sqrt5) / 2) ** (n + 1) - ((1 - sqrt5) / 2) ** (n + 1)) / sqrt5;
}

這個其實牽涉到V8的一些機制。V8作爲著名的JIT引擎,會對“熱點代碼”進行編譯,轉化成彙編代碼。也就是說,反覆執行的代碼就有概率會被編譯成彙編;反覆執行就是爲了提醒v8,我這個代碼你可以優化。即使是重複執行幾次這種級別的彙編碼,也比只執行一次的需要翻譯的JS代碼要快。這其實是很有意思的一件事,我在之前的文章裏稍微提過一點。

但是整個過程是不能控制的,具體是否會被編譯,如何編譯,我們都無法控制,類似於數據庫的優化器,以及cpp的inline機制,只是一個“請求”而已。並且,如果由於種種原因,編譯過的代碼被放棄,會造成大量的反優化的開銷。所以爲了穩定考慮,一般還是會提取一個變量來避免重複運算;畢竟我們不能把寶押在JS引擎上,不是嗎。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章