## Chapter 9. General Programming(通用程序設計)
### Item 67: Optimize judiciously(明智地進行優化)
There are three aphorisms concerning optimization that everyone should know:
有三條關于優化的格言是每個人都應該知道的:
More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason—including blind stupidity.
比起其他任何單一的原因(包括盲目的愚蠢),很多計算上的過失都被歸昝于效率(不一定能實現)。
—William A. Wulf [Wulf72]
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
不要去計較效率上的一些小小的得失,在 97% 的情況下,不成熟的優化才是一切問題的根源。
—Donald E. Knuth [Knuth74]
We follow two rules in the matter of optimization:
Rule 1. Don’t do it.
Rule 2 (for experts only). Don’t do it yet—that is, not until you have a perfectly clear and unoptimized solution.
在優化方面,我們應該遵守兩條規則:
規則 1:不要進行優化。
規則 2 (僅針對專家):還是不要進行優化,也就是說,在你還沒有絕對清晰的未優化方案之前,請不要進行優化。
—M. A. Jackson [Jackson75]
All of these aphorisms predate the Java programming language by two decades. They tell a deep truth about optimization: it is easy to do more harm than good, especially if you optimize prematurely. In the process, you may produce software that is neither fast nor correct and cannot easily be fixed.
所有這些格言都比 Java 編程語言早了 20 年。它們告訴我們關于優化的一個深刻的事實:很容易弊大于利,尤其是如果過早地進行優化。在此過程中,你可能會生成既不快速也不正確且無法輕松修復的軟件。
Don’t sacrifice sound architectural principles for performance. Strive to write **good programs rather than fast ones.** If a good program is not fast enough, its architecture will allow it to be optimized. Good programs embody the principle of information hiding: where possible, they localize design decisions within individual components, so individual decisions can be changed without affecting the remainder of the system (Item 15).
不要為了性能而犧牲合理的架構。努力編寫 **好的程序,而不是快速的程序。** 如果一個好的程序不夠快,它的架構將允許它被優化。好的程序體現了信息隱藏的原則:在可能的情況下,它們在單個組件中本地化設計決策,因此可以在不影響系統其余部分的情況下更改單個決策([Item-15](/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。
This does not mean that you can ignore performance concerns until your program is complete. Implementation problems can be fixed by later optimization, but pervasive architectural flaws that limit performance can be impossible to fix without rewriting the system. Changing a fundamental facet of your design after the fact can result in an ill-structured system that is difficult to maintain and evolve. Therefore you must think about performance during the design process.
這并不意味著在程序完成之前可以忽略性能問題。實現上的問題可以通過以后的優化來解決,但是對于架構缺陷,如果不重寫系統,就不可能解決限制性能的問題。在系統完成之后再改變設計的某個基本方面可能導致結構不良的系統難以維護和進化。因此,你必須在設計過程中考慮性能。
**Strive to avoid design decisions that limit performance.** The components of a design that are most difficult to change after the fact are those specifying interactions between components and with the outside world. Chief among these design components are APIs, wire-level protocols, and persistent data formats. Not only are these design components difficult or impossible to change after the fact, but all of them can place significant limitations on the performance that a system can ever achieve.
**盡量避免限制性能的設計決策。** 設計中最難以更改的組件是那些指定組件之間以及與外部世界的交互的組件。這些設計組件中最主要的是 API、線路層協議和持久數據格式。這些設計組件不僅難以或不可能在事后更改,而且所有這些組件都可能對系統能夠達到的性能造成重大限制。
**Consider the performance consequences of your API design decisions.** Making a public type mutable may require a lot of needless defensive copying (Item 50). Similarly, using inheritance in a public class where composition would have been appropriate ties the class forever to its superclass, which can place artificial limits on the performance of the subclass (Item 18). As a final example, using an implementation type rather than an interface in an API ties you to a specific implementation, even though faster implementations may be written in the future (Item 64).
**考慮API設計決策的性能結果。** 使公共類型轉化為可變,可能需要大量不必要的防御性復制([Item-50](/Chapter-8/Chapter-8-Item-50-Make-defensive-copies-when-needed.md))。類似地,在一個公共類中使用繼承(在這個類中組合將是合適的)將該類永遠綁定到它的超類,這會人為地限制子類的性能([Item-18](/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md))。最后一個例子是,在 API 中使用實現類而不是接口將你綁定到特定的實現,即使將來可能會編寫更快的實現也無法使用([Item-64](/Chapter-9/Chapter-9-Item-64-Refer-to-objects-by-their-interfaces.md))。
The effects of API design on performance are very real. Consider the getSize method in the java.awt.Component class. The decision that this performance-critical method was to return a Dimension instance, coupled with the decision that Dimension instances are mutable, forces any implementation of this method to allocate a new Dimension instance on every invocation. Even though allocating small objects is inexpensive on a modern VM, allocating millions of objects needlessly can do real harm to performance.
API 設計對性能的影響是非常實際的。考慮 `java.awt.Component` 中的 getSize 方法。該性能很關鍵方法返回 Dimension 實例的決定,加上維度實例是可變的決定,強制該方法的任何實現在每次調用時分配一個新的 Dimension 實例。盡管在現代 VM 上分配小對象并不昂貴,但不必要地分配數百萬個對象也會對性能造成實際損害。
Several API design alternatives existed. Ideally, Dimension should have been immutable (Item 17); alternatively, getSize could have been replaced by two methods returning the individual primitive components of a Dimension object. In fact, two such methods were added to Component in Java 2 for performance reasons. Preexisting client code, however, still uses the getSize method and still suffers the performance consequences of the original API design decisions.
存在幾種 API 設計替代方案。理想情況下,Dimension 應該是不可變的([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md));或者,getSize 可以被返回 Dimension 對象的原始組件的兩個方法所替代。事實上,出于性能原因,在 Java 2 的組件中添加了兩個這樣的方法。然而,現有的客戶端代碼仍然使用 getSize 方法,并且仍然受到原始 API 設計決策的性能影響。
Luckily, it is generally the case that good API design is consistent with good performance. **It is a very bad idea to warp an API to achieve good performance.** The performance issue that caused you to warp the API may go away in a future release of the platform or other underlying software, but the warped API and the support headaches that come with it will be with you forever.
幸運的是,通常情況下,好的 API 設計與好的性能是一致的。**為了獲得良好的性能而改變 API 是一個非常糟糕的想法。** 導致你改變 API 的性能問題,可能在平臺或其他底層軟件的未來版本中消失,但是改變的 API 和隨之而來的問題將永遠伴隨著你。
Once you’ve carefully designed your program and produced a clear, concise, and well-structured implementation, then it may be time to consider optimization, assuming you’re not already satisfied with the performance of the program.
一旦你仔細地設計了你的程序,成了一個清晰、簡潔、結構良好的實現,那么可能是時候考慮優化了,假設此時你還不滿意程序的性能。
Recall that Jackson’s two rules of optimization were “Don’t do it,” and “(for experts only). Don’t do it yet.” He could have added one more: **measure performance before and after each attempted optimization.** You may be surprised by what you find. Often, attempted optimizations have no measurable effect on performance; sometimes, they make it worse. The main reason is that it’s difficult to guess where your program is spending its time. The part of the program that you think is slow may not be at fault, in which case you’d be wasting your time trying to optimize it. Common wisdom says that programs spend 90 percent of their time in 10 percent of their code.
記得 Jackson 的兩條優化規則是「不要做」和「(只針對專家)」。先別這么做。他本可以再加一個:**在每次嘗試優化之前和之后測量性能。** 你可能會對你的發現感到驚訝。通常,試圖做的優化通常對于性能并沒有明顯的影響;有時候,還讓事情變得更糟。主要原因是很難猜測程序將時間花費在哪里。程序中你認為很慢的部分可能并沒有問題,在這種情況下,你是在浪費時間來優化它。一般認為,程序將 90% 的時間花費在了 10% 的代碼上。
Profiling tools can help you decide where to focus your optimization efforts. These tools give you runtime information, such as roughly how much time each method is consuming and how many times it is invoked. In addition to focusing your tuning efforts, this can alert you to the need for algorithmic changes. If a quadratic (or worse) algorithm lurks inside your program, no amount of tuning will fix the problem. You must replace the algorithm with one that is more efficient. The more code in the system, the more important it is to use a profiler. It’s like looking for a needle in a haystack: the bigger the haystack, the more useful it is to have a metal detector. Another tool that deserves special mention is jmh, which is not a profiler but a microbenchmarking framework that provides unparalleled visibility into the detailed performance of Java code [JMH].
分析工具可以幫助你決定將優化工作的重點放在哪里。這些工具提供了運行時信息,比如每個方法大約花費多少時間以及調用了多少次。除了關注你的調優工作之外,這還可以提醒你是否需要改變算法。如果程序中潛伏著平方級(或更差)的算法,那么再多的調優也無法解決這個問題。你必須用一個更有效的算法來代替這個算法。系統中的代碼越多,使用分析器就越重要。這就像大海撈針:大海越大,金屬探測器就越有用。另一個值得特別提及的工具是 jmh,它不是一個分析器,而是一個微基準測試框架,提供了對 Java 代碼性能無與倫比的預測性。
The need to measure the effects of attempted optimization is even greater in Java than in more traditional languages such as C and C++, because Java has a weaker performance model: The relative cost of the various primitive operations is less well defined. The “abstraction gap” between what the programmer writes and what the CPU executes is greater, which makes it even more difficult to reliably predict the performance consequences of optimizations. There are plenty of performance myths floating around that turn out to be half-truths or outright lies.
與 C 和 C++ 等更傳統的語言相比,Java 甚至更需要度量嘗試優化的效果,因為 Java 的性能模型更弱:各種基本操作的相對成本沒有得到很好的定義。程序員編寫的內容和 CPU 執行的內容之間的「抽象鴻溝」更大,這使得可靠地預測優化的性能結果變得更加困難。有很多關于性能的傳說流傳開來,但最終被證明是半真半假或徹頭徹尾的謊言。
Not only is Java’s performance model ill-defined, but it varies from implementation to implementation, from release to release, and from processor to processor. If you will be running your program on multiple implementations or multiple hardware platforms, it is important that you measure the effects of your optimization on each. Occasionally you may be forced to make trade-offs between performance on different implementations or hardware platforms.
Java 的性能模型不僅定義不清,而且在不同的實現、不同的發布版本、不同的處理器之間都有所不同。如果你要在多個實現或多個硬件平臺上運行程序,那么度量優化對每個平臺的效果是很重要的。有時候,你可能會被迫在不同實現或硬件平臺上的性能之間進行權衡。
In the nearly two decades since this item was first written, every component of the Java software stack has grown in complexity, from processors to VMs to libraries, and the variety of hardware on which Java runs has grown immensely. All of this has combined to make the performance of Java programs even less predictable now than it was in 2001, with a corresponding increase in the need to measure it.
自本條目首次編寫以來的近 20 年里,Java 軟件棧的每個組件都變得越來越復雜,從處理器到 vm 再到庫,Java 運行的各種硬件都有了極大的增長。所有這些加在一起,使得 Java 程序的性能比 2001 年更難以預測,而對它進行度量的需求也相應增加。
To summarize, do not strive to write fast programs—strive to write good ones; speed will follow. But do think about performance while you’re designing systems, especially while you’re designing APIs, wire-level protocols, and persistent data formats. When you’ve finished building the system, measure its performance. If it’s fast enough, you’re done. If not, locate the source of the problem with the aid of a profiler and go to work optimizing the relevant parts of the system. The first step is to examine your choice of algorithms: no amount of low-level optimization can make up for a poor choice of algorithm. Repeat this process as necessary, measuring the performance after every change, until you’re satisfied.
總而言之,不要努力寫快的程序,要努力寫好程序;速度自然會提高。但是在設計系統時一定要考慮性能,特別是在設計API、線路層協議和持久數據格式時。當你完成了系統的構建之后,請度量它的性能。如果足夠快,就完成了。如果沒有,利用分析器找到問題的根源,并對系統的相關部分進行優化。第一步是檢查算法的選擇:再多的底層優化也不能彌補算法選擇的不足。根據需要重復這個過程,在每次更改之后測量性能,直到你滿意為止。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-9/Chapter-9-Introduction.md)**
- **Previous Item(上一條目):[Item 66: Use native methods judiciously(明智地使用本地方法)](/Chapter-9/Chapter-9-Item-66-Use-native-methods-judiciously.md)**
- **Next Item(下一條目):[Item 68: Adhere to generally accepted naming conventions(遵守被廣泛認可的命名約定)](/Chapter-9/Chapter-9-Item-68-Adhere-to-generally-accepted-naming-conventions.md)**
- Chapter 2. Creating and Destroying Objects(創建和銷毀對象)
- Item 1: Consider static factory methods instead of constructors(考慮以靜態工廠方法代替構造函數)
- Item 2: Consider a builder when faced with many constructor parameters(在面對多個構造函數參數時,請考慮構建器)
- Item 3: Enforce the singleton property with a private constructor or an enum type(使用私有構造函數或枚舉類型實施單例屬性)
- Item 4: Enforce noninstantiability with a private constructor(用私有構造函數實施不可實例化)
- Item 5: Prefer dependency injection to hardwiring resources(依賴注入優于硬連接資源)
- Item 6: Avoid creating unnecessary objects(避免創建不必要的對象)
- Item 7: Eliminate obsolete object references(排除過時的對象引用)
- Item 8: Avoid finalizers and cleaners(避免使用終結器和清除器)
- Item 9: Prefer try with resources to try finally(使用 try-with-resources 優于 try-finally)
- Chapter 3. Methods Common to All Objects(對象的通用方法)
- Item 10: Obey the general contract when overriding equals(覆蓋 equals 方法時應遵守的約定)
- Item 11: Always override hashCode when you override equals(當覆蓋 equals 方法時,總要覆蓋 hashCode 方法)
- Item 12: Always override toString(始終覆蓋 toString 方法)
- Item 13: Override clone judiciously(明智地覆蓋 clone 方法)
- Item 14: Consider implementing Comparable(考慮實現 Comparable 接口)
- Chapter 4. Classes and Interfaces(類和接口)
- Item 15: Minimize the accessibility of classes and members(盡量減少類和成員的可訪問性)
- Item 16: In public classes use accessor methods not public fields(在公共類中,使用訪問器方法,而不是公共字段)
- Item 17: Minimize mutability(減少可變性)
- Item 18: Favor composition over inheritance(優先選擇復合而不是繼承)
- Item 19: Design and document for inheritance or else prohibit it(繼承要設計良好并且具有文檔,否則禁止使用)
- Item 20: Prefer interfaces to abstract classes(接口優于抽象類)
- Item 21: Design interfaces for posterity(為后代設計接口)
- Item 22: Use interfaces only to define types(接口只用于定義類型)
- Item 23: Prefer class hierarchies to tagged classes(類層次結構優于帶標簽的類)
- Item 24: Favor static member classes over nonstatic(靜態成員類優于非靜態成員類)
- Item 25: Limit source files to a single top level class(源文件僅限有單個頂層類)
- Chapter 5. Generics(泛型)
- Item 26: Do not use raw types(不要使用原始類型)
- Item 27: Eliminate unchecked warnings(消除 unchecked 警告)
- Item 28: Prefer lists to arrays(list 優于數組)
- Item 29: Favor generic types(優先使用泛型)
- Item 30: Favor generic methods(優先使用泛型方法)
- Item 31: Use bounded wildcards to increase API flexibility(使用有界通配符增加 API 的靈活性)
- Item 32: Combine generics and varargs judiciously(明智地合用泛型和可變參數)
- Item 33: Consider typesafe heterogeneous containers(考慮類型安全的異構容器)
- Chapter 6. Enums and Annotations(枚舉和注解)
- Item 34: Use enums instead of int constants(用枚舉類型代替 int 常量)
- Item 35: Use instance fields instead of ordinals(使用實例字段替代序數)
- Item 36: Use EnumSet instead of bit fields(用 EnumSet 替代位字段)
- Item 37: Use EnumMap instead of ordinal indexing(使用 EnumMap 替換序數索引)
- Item 38: Emulate extensible enums with interfaces(使用接口模擬可擴展枚舉)
- Item 39: Prefer annotations to naming patterns(注解優于命名模式)
- Item 40: Consistently use the Override annotation(堅持使用 @Override 注解)
- Item 41: Use marker interfaces to define types(使用標記接口定義類型)
- Chapter 7. Lambdas and Streams(λ 表達式和流)
- Item 42: Prefer lambdas to anonymous classes(λ 表達式優于匿名類)
- Item 43: Prefer method references to lambdas(方法引用優于 λ 表達式)
- Item 44: Favor the use of standard functional interfaces(優先使用標準函數式接口)
- Item 45: Use streams judiciously(明智地使用流)
- Item 46: Prefer side effect free functions in streams(在流中使用無副作用的函數)
- Item 47: Prefer Collection to Stream as a return type(優先選擇 Collection 而不是流作為返回類型)
- Item 48: Use caution when making streams parallel(謹慎使用并行流)
- Chapter 8. Methods(方法)
- Item 49: Check parameters for validity(檢查參數的有效性)
- Item 50: Make defensive copies when needed(在需要時制作防御性副本)
- Item 51: Design method signatures carefully(仔細設計方法簽名)
- Item 52: Use overloading judiciously(明智地使用重載)
- Item 53: Use varargs judiciously(明智地使用可變參數)
- Item 54: Return empty collections or arrays, not nulls(返回空集合或數組,而不是 null)
- Item 55: Return optionals judiciously(明智地的返回 Optional)
- Item 56: Write doc comments for all exposed API elements(為所有公開的 API 元素編寫文檔注釋)
- Chapter 9. General Programming(通用程序設計)
- Item 57: Minimize the scope of local variables(將局部變量的作用域最小化)
- Item 58: Prefer for-each loops to traditional for loops(for-each 循環優于傳統的 for 循環)
- Item 59: Know and use the libraries(了解并使用庫)
- Item 60: Avoid float and double if exact answers are required(若需要精確答案就應避免使用 float 和 double 類型)
- Item 61: Prefer primitive types to boxed primitives(基本數據類型優于包裝類)
- Item 62: Avoid strings where other types are more appropriate(其他類型更合適時應避免使用字符串)
- Item 63: Beware the performance of string concatenation(當心字符串連接引起的性能問題)
- Item 64: Refer to objects by their interfaces(通過接口引用對象)
- Item 65: Prefer interfaces to reflection(接口優于反射)
- Item 66: Use native methods judiciously(明智地使用本地方法)
- Item 67: Optimize judiciously(明智地進行優化)
- Item 68: Adhere to generally accepted naming conventions(遵守被廣泛認可的命名約定)
- Chapter 10. Exceptions(異常)
- Item 69: Use exceptions only for exceptional conditions(僅在確有異常條件下使用異常)
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors(對可恢復情況使用 checked 異常,對編程錯誤使用運行時異常)
- Item 71: Avoid unnecessary use of checked exceptions(避免不必要地使用 checked 異常)
- Item 72: Favor the use of standard exceptions(鼓勵復用標準異常)
- Item 73: Throw exceptions appropriate to the abstraction(拋出能用抽象解釋的異常)
- Item 74: Document all exceptions thrown by each method(為每個方法記錄會拋出的所有異常)
- Item 75: Include failure capture information in detail messages(異常詳細消息中應包含捕獲失敗的信息)
- Item 76: Strive for failure atomicity(盡力保證故障原子性)
- Item 77: Don’t ignore exceptions(不要忽略異常)
- Chapter 11. Concurrency(并發)
- Item 78: Synchronize access to shared mutable data(對共享可變數據的同步訪問)
- Item 79: Avoid excessive synchronization(避免過度同步)
- Item 80: Prefer executors, tasks, and streams to threads(Executor、task、流優于直接使用線程)
- Item 81: Prefer concurrency utilities to wait and notify(并發實用工具優于 wait 和 notify)
- Item 82: Document thread safety(文檔應包含線程安全屬性)
- Item 83: Use lazy initialization judiciously(明智地使用延遲初始化)
- Item 84: Don’t depend on the thread scheduler(不要依賴線程調度器)
- Chapter 12. Serialization(序列化)
- Item 85: Prefer alternatives to Java serialization(優先選擇 Java 序列化的替代方案)
- Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)
- Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式)
- Item 88: Write readObject methods defensively(防御性地編寫 readObject 方法)
- Item 89: For instance control, prefer enum types to readResolve(對于實例控制,枚舉類型優于 readResolve)
- Item 90: Consider serialization proxies instead of serialized instances(考慮以序列化代理代替序列化實例)