讀古今文學網 > Java程序員修煉之道 > 6.3 哪裡出錯了?我們擔心的原因 >

6.3 哪裡出錯了?我們擔心的原因

在幾年前,性能問題看起來並不重要。時鐘速度不斷上升,所有軟件工程師只要多等幾個月,哪怕是寫得很爛的代碼,也能借助節節攀升的CPU速度表現出優異性能。

那麼,事情怎麼會錯得這麼離譜?為什麼時鐘速度的提升不再那麼快了?更讓人擔憂的是,為什麼有3GHz芯片的電腦看起來比2GHz芯片的快不了多少?行業中軟件工程師需要考慮性能問題的這種趨勢是從哪裡來的?

我們會在本節討論引導這股趨勢的力量,以及連最純粹的軟件開發人員也需要瞭解一點硬件知識的原因。我們會為本章剩下的內容打好基礎,讓你理解JIT編譯的概念和一些有深度的例子。

你可能聽說過「摩爾定律」。很多開發人員都知道這個定律與提高計算機速度有關,但對於具體細節不甚了了。我們來解釋一下它到底是什麼意思,以及它在不久的將來可能帶來的影響。

6.3.1 過去和未來的性能趨勢:摩爾定律

摩爾定律是Gordon Moore提出來的,他是Intel的創始人之一。該定律最常見的形式之一是:晶片上的晶體管數量每兩年翻一番是合算的。

這個定律實際上是對計算機處理器(CPU)發展趨勢的一種看法,基於Gordon Moore在1965年發表的一篇論文,最初是對未來10年進行預測——也就是直到1975年。而這一預測直到現在仍然能夠應驗(據預測其在2015年以前都有效),實在值得稱道。

我們在圖6-2中繪製了Intel x86家族從1980年發展到2010年的i7整個歷程中的一些真實CPU。圖中顯示了不同時期發佈的芯片所集成的晶體管數量。

圖6-2 隨時間變化的晶體管數量對數線性圖

這是一個對數線性圖,所以y軸上的每次增長都是前一個值的10倍。如你所見,這條線基本是直的,而且每隔六或七年就會穿過一個垂直層級。這證明了摩爾定律的準確性,因為六或七年增長十倍和每隔兩年翻一番基本是一致的。

圖中y軸的刻度是對數,這就是說Intel在2005年生產的主流新品大概有一億個晶體管。這是1990年生產的芯片上的晶體管數量的100倍。

一定要注意摩爾定律特別談到了晶體管數量。要知道,單憑摩爾定律不足以讓軟件工程師繼續從硬件工程師那裡得到好處,理解這一點是最基本的要求。

注意 晶體管數量和時鐘速度不是一回事兒,人們一般會認為更高的時鐘速度就意味著更好的性能,其實這是一種過於草率的想法。

摩爾定律在過去很有指導意義,並且在未來一段時間內應該仍然準確(有不同的估算,但在2015年前看起來是合理的)。但摩爾定律計算的是晶體管數量,用這個數值來指導開發人員提高代碼性能越來越不靠譜。實際上,你會發現情況要更複雜。

在實際操作中,性能是由一系列因素共同決定的,而且每個因素都很重要。然而如果硬讓我們從裡面挑一個,應該是定位指令與數據運行速度的相關性。這個概念對性能非常重要,我們會深入瞭解。

6.3.2 理解內存延遲層級

計算機處理器要處理數據。如果它要處理的數據到不了,CPU時鐘多快都沒用——它只能等著,執行空操作(NOP),在數據到來之前基本就處於停轉狀態。

也就是說在解決延遲時,最重要的兩個問題是,「CPU核心要處理的數據的最近的復本在哪裡?」,還有「把它送到核心能用的地方需要多長時間?」主要有以下幾種答案。

  • 寄存器:這是CPU上的內存地址,隨時可用。這部分內存是指令直接操作的。

  • 主存:這一般是DRAM。訪問時間在50納秒左右(關於如何使用處理器緩存避免這段延遲請參見後續內容)。

  • 固態磁盤(SSD):訪問這種磁盤所需的時間不足0.1毫秒,但跟傳統硬盤比起來,它們要便宜一些。

  • 硬盤:訪問這種磁盤並把數據加載到主存中大概需要5毫秒。

摩爾定律預測了晶體管數量的指數級增長,這對內存也有好處——內存訪問速度也能以指數級增長。但這兩種指數並不相同。內存速度的提升比CPU晶體管數量的增長要慢得多,這意味著核心遲早會因為沒有相關數據可以處理而落入空閒狀態。

為了解決這個問題,在寄存器和主存之間引入了緩存。緩存是少量更快的內存(SRAM,而不是DRAM)。這種更快的內存成本要比DRAM高很多(無論是金錢還是晶體管),所以計算機不全用SRAM作為內存。

緩存分為一級緩存(L1)和二級緩存(L2)(某些機器還有L3),數值表明緩存到CPU的距離(越近越快)。我們會在6.6節(在JIT編譯上)詳細討論緩存,並給出一個例子來表明L1緩存對運行代碼的重要影響。圖6-3展示了L1和L2緩存比主存快多少。之後,我們還會給出一個例子來闡明這些速度差異對運行代碼的性能有什麼影響。

圖6-3 寄存器、處理器緩存和主存的相對訪問時間

除了增加緩存,20世紀90年代到21世紀早期大量使用了另外一種技術解決內存延遲的問題,就是增加處理器的功能,這使得處理器越來越複雜。即便CPU處理能力和內存延遲之間的差距越來越大,也仍然採用複雜的硬件技術來保證CPU有數據可以處理,比如指令級並行(ILP)和芯片多線程(CMT)。

這些技術的出現消耗了CPU晶體管預算中的大部分,並且它們使真實性能收益遞減。這一趨勢導致了新觀點的出現,即在未來設計帶有多個(或很多)核心的CPU芯片。

這意味著未來的性能和並發密切相關——主要辦法之一就是通過擁有更多核心讓系統整體性能得到提升。那樣,即便有一個核心在等待數據,其他核心也可以繼續工作。這種關係十分重要,所以我們要一再提起。

  • 將來的CPU是多核的。
  • 性能和並發綁在一起變成了相同的關注點。

Java程序員除了要關注硬件,還要注意JVM特性帶來了額外的複雜性。下一節我們就來看一下這些內容。

6.3.3 為什麼Java性能調優存在困難

在JVM或其他任何受控運行時環境上做性能調優天生就比在非受控環境下做調優困難。這是因為C/C++程序員幾乎所有事情都要自己做。OS只提供很少的服務,比如基本的線程調度。

在受控系統中,基本觀點是讓運行時來控制環境,不用開發人員自己處理所有細節。這能提高程序員的生產率,但要放棄某些控制權。另外一種選擇是放棄受控運行時提供的所有便利,但和性能調優所做的工作相比,這個代價實在太高了。

造成調優困難的平台特性主要是:

  • 線程調度;
  • 垃圾收集(GC);
  • 即時(JIT)編譯。

這些特性能以很巧妙的方式交互。例如,編譯子系統用計時器來決定編譯哪個方法。也就是說等待編譯的候選方法集可能會受到調度和GC等特性的影響。每次運行時所編譯的方法可能都不同。

正如你在本節中看到的,準確測量是性能分析決策過程的關鍵。如果你決定認真對待性能調優,那麼理解Java平台中處理時間的細節和限制就非常有用。