メインコンテンツまでスキップ

コラム: 早すぎる最適化は諸悪の根源

「Premature optimization is the root of all evil」 ― Donald Knuth


この格言の前提

┌─────────────────────────────────────────────────────────────┐
│ 「性能を無視していい」という意味ではない │
├─────────────────────────────────────────────────────────────┤
│ │
│ この格言は、基本的な設計が正しいことが前提 │
│ │
│ 最初から行うべきこと(これは「最適化」ではなく「設計」): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・適切なアルゴリズムを選ぶ(O(N²) → O(N log N)) │ │
│ │ ・適切なデータ構造を選ぶ(List → HashSet) │ │
│ │ ・N+1問題を避ける │ │
│ │ ・DBのテーブル設計を適切に行う │ │
│ │ ・DBのインデックスを適切に設計する │ │
│ │ ・接続プールを使う │ │
│ │ ・アーキテクチャを正しく設計する │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 「早すぎる最適化」が指すのは: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・計測せずに「ここが遅そう」と推測で最適化 │ │
│ │ ・可読性を犠牲にした細かいチューニング │ │
│ │ ・問題になっていない箇所の改善 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 「最適化しない」≠「非効率を放置する」 │
│ │
└─────────────────────────────────────────────────────────────┘

有名な格言

┌─────────────────────────────────────────────────────────────┐
│ 完全な引用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ "We should forget about small efficiencies, │
│ say about 97% of the time: │
│ premature optimization is the root of all evil. │
│ Yet we should not pass up our opportunities │
│ in that critical 3%." │
│ │
│ 「小さな効率については、97%の場合において │
│ 忘れるべきだ。早すぎる最適化は諸悪の根源である。 │
│ しかし、重要な3%の機会を逃してはならない。」 │
│ │
│ ― Donald Knuth, 1974 │
│ │
└─────────────────────────────────────────────────────────────┘

なぜ「早すぎる最適化」が問題か

┌─────────────────────────────────────────────────────────────┐
│ 早すぎる最適化の害 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 間違った場所を最適化してしまう │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・推測でボトルネックを決めつける │ │
│ │ ・実際のボトルネックは別の場所 │ │
│ │ ・効果のない最適化に時間を浪費 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. コードが複雑になる │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・可読性の低下 │ │
│ │ ・保守性の低下 │ │
│ │ ・バグの温床 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. 開発速度が落ちる │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・最適化に時間を取られる │ │
│ │ ・本来の機能開発が遅れる │ │
│ │ ・変更しにくくなる │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. 最適化の前提が変わる │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・要件が変わって最適化が無駄になる │ │
│ │ ・データ量が変わって効果がなくなる │ │
│ │ ・ハードウェアが変わって不要になる │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

いつ最適化すべきか

┌─────────────────────────────────────────────────────────────┐
│ 最適化のタイミング │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ 最適化すべきでない時: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・「たぶん遅くなりそう」という推測 │ │
│ │ ・まだ動くものがない段階 │ │
│ │ ・計測していない │ │
│ │ ・問題になっていない │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ✅ 最適化すべき時: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・計測して遅いと分かった │ │
│ │ ・ユーザーに影響が出ている │ │
│ │ ・SLOを満たせない │ │
│ │ ・コストが問題になっている │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ 例外(最初から考慮すべきこと): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・O(N²)を避けるなど、明らかに非効率なアルゴリズム │ │
│ │ ・N+1問題など、よく知られたアンチパターン │ │
│ │ ・アーキテクチャレベルの決定(後から変えにくい) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

正しいアプローチ

┌─────────────────────────────────────────────────────────────┐
│ Make it work, make it right, make it fast │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. Make it work(まず動かす) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・正しく動くコードを書く │ │
│ │ ・テストを書く │ │
│ │ ・パフォーマンスは後回し │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. Make it right(きれいにする) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・リファクタリング │ │
│ │ ・可読性の向上 │ │
│ │ ・重複の除去 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. Make it fast(速くする) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・計測してボトルネックを特定 │ │
│ │ ・必要な箇所だけ最適化 │ │
│ │ ・効果を計測で確認 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ※ Kent Beck の言葉とされる │
│ │
└─────────────────────────────────────────────────────────────┘

「重要な3%」の見分け方

┌─────────────────────────────────────────────────────────────┐
│ 最初から考慮すべき「3%」 │
├─────────────────────────────────────────────────────────────┤
│ │
│ アルゴリズムの選択: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・O(N²) vs O(N log N) は後から変えにくい │ │
│ │ ・データ量が増えた時に破綻する │ │
│ │ ・最初から適切なアルゴリズムを選ぶ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ データ構造の選択: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・検索が多いなら HashSet/HashMap │ │
│ │ ・順序が必要なら TreeSet/TreeMap │ │
│ │ ・後から変えると影響が大きい │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ アーキテクチャの選択: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ・同期 vs 非同期 │ │
│ │ ・モノリス vs マイクロサービス │ │
│ │ ・キャッシュ戦略 │ │
│ │ ・後から変えるのは非常にコストが高い │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ これらは「最適化」ではなく「設計」の問題 │
│ │
└─────────────────────────────────────────────────────────────┘

よくある誤り

┌─────────────────────────────────────────────────────────────┐
│ こういう最適化は待て │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ 「このループは遅そうだから最適化しよう」 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → 計測してから。1000回ループでも1ms未満なら問題ない│ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ❌ 「オブジェクト生成を減らそう」 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → 可読性を犠牲にしてまで?GCは優秀 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ❌ 「キャッシュを入れておこう」 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → キャッシュは複雑さを増す。必要になってから │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ❌ 「非同期にしておこう」 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ → 非同期は難しい。同期で問題になってから │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

まとめ

┌─────────────────────────────────────────────────────────────┐
│ 最適化の心得 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. まず計測する │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 推測しない。データに基づいて判断する。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. ボトルネックを特定する │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 全体の1%を改善しても1%しか速くならない。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. 効果を計測する │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 最適化の前後で計測して、効果を確認する。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 4. 可読性とのバランス │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 10%の高速化のために可読性を大きく損なうなら、 │ │
│ │ その最適化は本当に必要か考える。 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 覚えておくこと: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 「動くコード」は「速いコード」より価値がある │ │
│ │ 「読めるコード」は「速いコード」より価値がある │ │
│ │ 必要になった時に最適化すればいい │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘