67.最適化は気を付けて行うべし
速いプログラムよりも良いプログラムを目指す
プログラムの最適化を安易に行わないように促す格言は、昔からある。
最適化のために健全なアーキテクチャを破壊してはならない。
健全なアーキテクチャが保てているのであれば、個々のコンポーネントは疎な関係を保てているはずなので、それらは後から他には影響を与えずに変更することができる。
かといって、プログラムが完成するまで性能のことを考えなくてよいわけではない。
普及したアーキテクチャの欠陥を修正することはほぼ無理である。
そのため、設計の段階で性能に関する考慮は十分にしておかなければならない。
性能を制限してしまう設計は避ける
コンポーネントの設計で、一度決まった後に変更するが一番困難なのは、外界とコンポーネントの境界部分である。
主な例としては、
- API
- wire-levelのプロトコル(SOAPとかCORBAとか)
- 永続化データの形式
などである。
API設計での性能について考える
API設計において、パフォーマンスについて考えねばならない。
- publicな型をmutableにすると、不必要なdefensive copyがたくさん必要になるかもしれない(Item50)。
- publicクラスに継承を用いるとスーパークラスによって性能制限を受け続けるかもしれない(Item18)(いまいちよくわからない)。
- APIにインターフェースでなく、実装型を用意した場合には、より良い実装が未来になされた場合に変更しにくい(Item64)。
実例として、java.awt.Component クラスのgetSizeメソッドは呼び出すたびにmutableな Dimension インターフェースを生成することになっており、性能影響が出てしまう。
本来的には、Dimensionはimmutable(Item17)であるべきであった。また、getSizeメソッドは個々のDimensionオブジェクトのコンポーネントを返す2つのメソッドに置き換えられるべきであった(ぴんと来ない)。
実際、Componentクラスに、そのようなメソッドがJava2では用意されたが、以前からgetSizeを用いているコードはなおも性能問題を抱えている。
一般に、良いAPI設計には良いパフォーマンスがついてくるものである。
パフォーマンス向上のためにAPIをゆがませるのは筋が悪い。
そこで得られたパフォーマンス向上は、将来のプラットフォーム変更で消し飛びうるが、ゆがんだAPIを保守する難点はずっと抱えていかねばならない。
最適化
うまく設計、実装ができた後になお、性能向上が必用であった場合には、最適化を試みる。
パフォーマンスを測る
最適化をした前後でパフォーマンス測定を行う必要がある。
どのプログラムの実行に時間がかかっているか予測するのは困難であるため、時間を無駄にしないためにも計測をしながら最適化をすすめていく。
プロファイリングツール
プロファイリングツールを利用することで、どのメソッドがどれくらい時間がかかり、何回呼ばれているかが分かる。
この時に、アルゴリズムの選択が間違っていないか見ることができる。
アルゴリズムの選択によって、性能は劇的に変わるため、適切なものにする。
また、マイクロベンチマーキングフレームワークである、JMHというツールもある。
これにより、Javaコードの詳細なパフォーマンス情報を可視化できる。
Javaにおける計測の必要性
Javaでの性能計測は、CやC++といった昔の言語よりもの必要性が大きい。
なぜなら、それらの言語に比べ、CPUで実行される処理と、コードで表現されていることの乖離が大きいからである。
また、Javaでは、実装、リリースのバージョン、プロセッサによって性能がことなる。
違うプラットフォーム上で動くことが分かったとしたら、各々の環境で性能測定をすることが重要である。
その結果として、環境ごとの性能のトレードオフが発生することがしばしばある。
Javaが動く環境はますます複雑化してきているので、パフォーマンスの予想はよりつかなくなってきている。
よって、測定の必要性も増してきていると言える。