Effective Java 第三版——67. 明智謹慎地進行優化

Tips
書中的源代碼地址:https://github.com/jbloch/effective-java-3e-source-code
注意,書中的有些代碼裏方法是基於Java 9 API中的,因此JDK 最好下載 JDK 9以上的版本。html

Effective Java, Third Edition

67. 明智謹慎地進行優化

關於優化有三個格言,每一個人都應該知道:java

  • 更多的計算上的過失是以效率的名義(不必定實現它)而不是任何其餘單一緣由——包括盲目作愚蠢的事情。
    ——William A. Wulf [Wulf72]
  • 咱們應該不去計較小小的效率,大約97%時間裏:過早的優化是全部問題的根源。
    ———Donald E. Knuth [Knuth74]

在優化方面,咱們遵循兩條規則:git

  • 規則1。不要優化。
  • 規則2(只適用於專家)。先不要優化——也就是說,直到你有了一個徹底清晰的還未優化的解決方案以前,不要優化。

全部這些格言都比Java編程語言的出現早二十年。 他們講述了優化的深層真理:特別是如你過早優化的話,弊大於利。 在此過程當中,可能會生成既不快,又不正確,且沒法輕鬆修復的軟件。程序員

不要爲了性能而犧牲合理的架構原則。努力編寫好的程序,而不是快的程序。若是一個好的程序不夠快,它的架構容許對其進行優化。好的程序體現了信息隱藏的原則:在可能的狀況下,他們設計決策本地化爲單個組件,所以能夠在不影響系統其他部分的狀況下更改單個決策(條目15)。github

這並不意味着能夠在程序完成以前忽略性能問題。 實現問題能夠經過之後的優化來解決,可是若是不重寫系統,就沒法修復限制性能的廣泛存在的架構缺陷。 過後改變設計的基本方面可能致使結構不良的系統難以維護和發展。 所以,必須在設計過程當中考慮性能。算法

儘可能避免限制性能的設計決策。設計中最難以更改的組件是那些指定組件之間以及與外部系統的交互的組件。這些設計組件中最主要的是API、線路層(wire-level)協議和持久化數據格式。這些設計組件不只難以或不可能在過後更改,並且全部這些組件均可能對系統可以達到的性能形成重大限制。編程

考慮API設計決策的性能影響。 使公共類型可變可能須要大量沒必要要的防護性拷貝(條目 50)。 相似地,在一個公共類中應該使用複用更爲合適,但依舊使用繼承會把該類永遠綁定到它的父類,這會人爲地限制子類的性能(第18項)。最後一個例子是,在API中使用實現類型而不是接口會把你綁定到特定的實現,即便未來可能會編寫更快的實現(條目 64)。架構

API設計對性能的影響是很是真實存在的。 考慮java.awt.Component類中的getSize方法。 這個性能關鍵方法決定,是返回Dimension實例,並且Dimension實例是可變的,強制此方法的任何實現都在每次調用時分配一個新的Dimension實例。 儘管在現代VM上分配小對象的成本很低,可是沒必要要地分配數百萬個對象會對性能形成實際損害。框架

存在幾種API設計替代方案。 理想狀況下,Dimension應該是不可變的(條目 17); 或者,getSize可能已被兩個返回Dimension對象的各個基本組件的方法所代替。 實際上,出於性能緣由,在Java 2中將兩個這樣的方法添加到Component類中。 可是,預先存在的客戶端代碼仍然使用getSize方法,而且仍然會受到原始API設計決策的性能影響。編程語言

幸運的是,一般狀況下,好的API設計與好的性能是一致的。爲了得到良好的性能而包裝API是一個很是糟糕的想法。致使包裝API的性能問題可能在平臺或其餘底層軟件的將來發型版本中消失,可是包裝API和隨之而來的支持問題將永遠伴隨着你。

一旦仔細設計了程序並生成了清晰,簡潔且結構良好的實現,那麼多是時候考慮優化,假設你對程序的性能還不是不滿意。

回想一下Jackson的兩條優化規則是「不要優化」和「(只針對專家)仍是先別優化」。他本能夠再加上一條:在每次嘗試優化以前和優化以後,要測量性能。你可能會對本身發現感到驚訝。一般,嘗試的優化對性能沒有可測量的影響;有時候,他們讓事情變得更糟。主要緣由是很難猜想程序將時間花在哪裏。程序中你認爲很慢的部分可能並無錯,在這種狀況下,浪費時間來優化它。通常認爲,程序將90%的時間花在10%的代碼上。

性能分析工具能夠幫助你決定將優化工做的重點放在哪裏。這些工具提供了運行時信息,好比每一個方法大約花費多少時間以及調用了多少次。除了關注調優工做以外,還能夠提醒你須要進行算法更改。若是程序中潛藏着平方級(或更糟)算法,那麼再多的調優也沒法解決這個問題。必須用一個更有效的算法來代替這個算法。系統中的代碼越多,使用分析工具就越重要。這就像大海撈針:大海撈針越大,金屬探測器就越有用。另外一個值得特別說起的工具是jmh,它不是一個分析工具,而是一個微基準測試框架,提供了非並行的可見對Java代碼的詳細性能 [JMH]。

與C和C++等更傳統的語言相比,Java甚至更須要度量嘗試優化的效果,由於Java的性能模型很弱:各類基本操做的相對成本沒有獲得很好的定義。程序員編寫的內容和CPU執行的內容之間的「抽象鴻溝(abstraction gap)」更大,這使得可靠地預測優化的性能結果變得更加困難。有很關於性能的說法流傳開來,但最終被證實是半真半假或徹頭徹尾的謊話。

Java的性能模型不只定義不清,並且在不一樣的實現之間、不一樣的發佈之間、不一樣的處理器之間都有所不一樣。若是要在多個實現或多個硬件平臺上運行程序,那麼度量優化對每一個平臺的效果是很重要的。有時候,可能會被迫在不一樣實現或硬件平臺上的性能之間進行權衡。

自本條目首次編寫以來的近20年裏,Java軟件堆棧的每一個組件都變得愈來愈複雜,從處理器到不一樣的虛擬機再到類庫,Java運行的各類硬件都有了極大的增加。全部這些加在一塊兒,使得Java程序的性能比2001年更難以預測,而對它進行度量的需求也相應增長。

總而言之,不要努力寫出快速的程序——努力寫出好的程序; 這樣速度將隨之而來。 可是在設計系統時要考慮性能,尤爲是在設計API,線級協議和持久化數據格式時。 完成系統構建後,請測量其性能。 若是它足夠快,你就完成了。 若是沒有,請藉助分析工具找到問題的根源,而後開始優化系統的相關部分。 第一步是檢查算法選擇:再多低級優化也不能夠彌補差的算法選擇。 根據須要重複此過程,在每次更改後測量性能,直到滿意爲止。

相關文章
相關標籤/搜索