13 人が閲覧(直近 30 日) 開発

Premature optimization is the root of all evil(早すぎる最適化は諸悪の根源)の意味と原典

Premature optimization is the root of all evil(早すぎる最適化は諸悪の根源)の意味と原典

「Premature optimization is the root of all evil(早すぎる最適化は諸悪の根源)」は、ドナルド・クヌース(Donald E. Knuth)が1974年12月の論文「Structured Programming with go to Statements」の268ページに書いた一文です。格言として単独で引かれることが多いものの、原文は「小さな効率化は97%の場面で忘れるべきだ」と前置きし、直後に「それでも決定的な3%の機会は逃すな」と続きます。

この記事では、原文の前後を逐語で確認し、同じ年のチューリング賞講演に出てくる別の言い回しとの違い、誤読されやすい点、そして「早すぎるかどうか」を計測で判断する手順を、Python 3.14での実測値つきで解説します。

まとめ:原文が求めているのは「計測してから3%に集中する」こと

  • 出典:クヌースの論文「Structured Programming with go to Statements」(ACM Computing Surveys 6巻4号、1974年12月、268ページ)。同じ月に掲載されたチューリング賞講演にも「(or at least most of it)」付きの形で出てきます。ホーアの言葉とされるのは、クヌースが1989年にHoare's dictumと書いたためです。
  • 意味:最適化の禁止ではありません。重要でない部分の速度に時間を使い、デバッグと保守を悪化させることを戒め、重要な3%の機会は逃すなと言っています。
  • 「早すぎる」の基準:どこが重要かを計測で特定する前、という意味です。クヌースは同じ段落で、計測ツールを使ってきたプログラマーの経験では直感による推測は外れると書いています。
  • 実務での手順:プロファイラで時間を食っている関数を特定してから手を入れます。本記事の例では処理時間の98%が1つの関数に集中しており、listをsetに替えるだけで5.777秒が0.041秒(5回の中央値)になりました。

以下、原文の逐語から順に確認します。

原文の逐語と日本語訳:97%と3%に挟まれた一文

論文の268ページで、該当の一文は次の段落に置かれています(ACM Computing Surveys掲載版から逐語で引用)。

Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. 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%の機会を逃してはならない」となります。

段落はさらに続き、優れたプログラマーは重要なコードを注意深く見るが、それはそのコードが特定された後だと述べます(but only after that code has been identified.)。格言だけを切り出すと、この「特定した後なら積極的に手を入れる」という後半が抜け落ちます。

なお「97%」「3%」は、クヌース自身がsay about(まあ、およそ)と前置きしているとおり目安の比率で、コード行数や処理時間を測った統計ではありません。重要な部分は少数だ、という程度に読むのが原文に忠実です。

「premature」と「root of all evil」の語感

検索では「early optimization is the root of all evil」という形も使われていますが、原文の語はprematureです。単に時期が早いという意味ではなく「機が熟す前の」という含みがあり、この論文では「重要な箇所が特定される前」を指します。

the root of all evilは、欽定訳聖書(KJV)テモテへの手紙一6章10節のFor the love of money is the root of all evilで広く知られる英語の定型句です。日本語では「諸悪の根源」と訳されるのが一般的で、「すべての悪の根」と直訳しても意味は変わりません。

出典の確認:1974年の論文と同年のチューリング賞講演

クヌースは1974年に、同じ趣旨の文を2つの文献に書いています。引用元によって文言が違うのはこのためです。

文献 掲載 該当の表現
Structured Programming with go to Statements ACM Computing Surveys 6巻4号 p.268 97%と3%の文脈で登場
Computer Programming as an Art Communications of the ACM 17巻12号 「(or at least most of it)」付き

後者は1974年11月11日にサンディエゴのACM年次大会で行われたチューリング賞の受賞講演で、同年12月号に「Computer Programming as an Art」として掲載されました。講演版の該当箇所は次のとおりです。

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

講演版は「すべての悪(少なくともその大半)の根源」と自ら範囲を緩めており、問題は効率を気にすること自体ではなく「間違った場所で、間違った時に」気にすることだと明示しています。直後にはWe shouldn't be penny wise and pound foolish(小銭を惜しんで大金を失うな)と続きます。

「ホーアの格言」と呼ばれる理由

この一文をアントニー・ホーア(C. A. R. Hoare)の言葉として紹介する記事もあります。発端はクヌース自身で、1989年の論文「The Errors of TeX」(Software: Practice and Experience 19巻7号、633ページ)に次の一文を書いています。

But I also knew, and forgot, Hoare's dictum that premature optimization is the root of all evil in programming.

TeXの字句解析で、ほとんどの場合は参照されない変数cur_csへの0の代入を省く最適化を入れていたところ、後の修正で新しい分岐を足したときにその前提を忘れ、不具合につながった経緯を振り返った箇所です。格言の実例をクヌースが自分のバグで示している点で、1974年の2文献よりも具体的です。

一方、1974年の2文献ではクヌースはこの文を誰にも帰属させずに書いており、1989年の文もホーアのどの著作かは挙げていません。出典として示すなら、確認できる1974年のクヌースの論文を挙げるのが確実です。

格言の誤読と最適化を認める条件

12%の改善を「決して小さくない」と書いた同じ論文

この格言は「最適化は後回しでよい」という主張の根拠として引かれがちですが、同じ268ページでクヌースは逆方向のことも書いています。探索ループを書き換えた例(Example 2から2aへの変更)で約12%速くなったことについて、次のように述べています。

In established engineering disciplines a 12 % improvement, easily obtained, is never considered marginal; and I believe the same viewpoint should prevail in software engineering

「確立された工学分野では、容易に得られる12%の改善は決して小さいとはみなされない。ソフトウェア工学でも同じ見方をすべきだ」という意味です。そのうえで、使い捨てのプログラムならそこまではしないが、品質の高いプログラムを作るときにそうした効率化を封じる道具には縛られたくない、と続けます。クヌースが否定したのは効率化ではなく、重要でない箇所への見当違いの努力です。

対になる「早すぎる悲観化」

検索では「premature pessimization」という語も使われています。これはハーブ・サッター(Herb Sutter)とアンドレイ・アレキサンドレスク(Andrei Alexandrescu)の『C++ Coding Standards』(2004年)の項目名で、Item 8がDon't optimize prematurely.、Item 9がDon't pessimize prematurely.と対になっています。

項目名が示すとおり、「早すぎる最適化をしない」ことは「わざわざ遅い書き方を選んでよい」ことを意味しません。同じくらい単純に書ける効率のよい方法があるなら、最初からそちらを選びます。後の章で扱うlistとsetの違いは、その典型です。

「早すぎる」かどうかの判断基準

クヌースの言う「小さな効率化」と、設計段階で決めるべき性能上の判断は別物です。後者まで「早すぎる最適化」として先送りすると、あとで直すコストが跳ね上がります。

判断の対象 計測前に決めてよいか
検索に使うデータ構造(listかsetか) 決めてよい
ループ内でのDB問い合わせやAPI呼び出し 決めてよい(まとめて取る)
要件にある応答時間の上限 決めてよい(設計の入力)
公開APIの形・DBスキーマ・保存形式 決めてよい(後から変えにくい)
凝ったアルゴリズムへの置き換え 計測後
キャッシュ層の追加 計測後
ループ展開などの局所的な書き換え 計測後

線引きの目安になるのが、ロブ・パイク(Rob Pike)が「Notes on Programming in C」に書いた規則です。Rule 3はFancy algorithms are slow when n is small, and n is usually small.(凝ったアルゴリズムはnが小さいと遅く、nはたいてい小さい)とし、nが頻繁に大きくなると分かるまでは凝るな、大きくなってもまず計測せよ、と続けます。つまり単純で適切なデータ構造を選ぶのは最初にやること、凝ったアルゴリズムや局所的な高速化は計測の後です。

「単純な設計を先に」という考え方は、KISS原則やYAGNI原則と同じ方向を向いています。

計測してから最適化する手順:cProfileでの実測

パイクの同じ文書のRule 1とRule 2は、クヌースの「直感による推測は外れる」を実務の手順に落としたものです。

Rule 1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you've proven that's where the bottleneck is.
Rule 2. Measure. Don't tune for speed until you've measured, and even then don't unless one part of the code overwhelms the rest.

Rule 2の後半「計測した後でも、コードの一部分が他を圧倒していない限りチューニングするな」が、クヌースの3%に対応します。

cProfileによる処理時間の長い関数の特定

2万件の注文から、停止中の顧客(7,143件)を除いてCSVの行を作る処理を例にします。文字列の整形が重そうに見えますが、推測せずにPython標準のcProfileで測ります。

import cProfile
import pstats
import random
import statistics
import time


def load_orders(n):
    random.seed(0)
    return [
        {"id": i, "customer": random.randrange(50_000), "amount": random.randrange(100, 10_000)}
        for i in range(n)
    ]


def to_csv_row(order):
    return f"{order['id']:08d},{order['customer']},{order['amount']}"


def is_allowed(order, blocked):
    return order["customer"] not in blocked


def export(orders, blocked):
    return [to_csv_row(o) for o in orders if is_allowed(o, blocked)]


orders = load_orders(20_000)
blocked_list = list(range(0, 50_000, 7))  # 7,143件の停止顧客

cProfile.run("export(orders, blocked_list)", "prof.out")
pstats.Stats("prof.out").sort_stats("tottime").print_stats(4)

# 計測するのはexportだけ(setの生成時間は含めない)。5回の中央値を出す
for label, blocked in [("list", blocked_list), ("set", set(blocked_list))]:
    times = []
    for _ in range(5):
        t0 = time.perf_counter()
        rows = export(orders, blocked)
        times.append(time.perf_counter() - t0)
    print(f"{label}: median {statistics.median(times):.3f}s rows={len(rows)}")

Python 3.14.7(macOS、Intel Core i9-9880H)で実行した出力の要部です。秒数は環境によって変わるので、手元では割合と順位を見てください。

         37138 function calls in 5.265 seconds

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    20000    5.175    0.000    5.175    0.000 prof_demo.py:20(is_allowed)
    17134    0.061    0.000    0.061    0.000 prof_demo.py:16(to_csv_row)
        1    0.027    0.027    5.263    5.263 prof_demo.py:24(export)

list: median 5.777s rows=17134
set: median 0.041s rows=17134

tottimeの読み方と所属判定の改善

tottime(その関数自身で使った時間)を見ると、全体5.265秒のうちis_allowedが5.175秒、約98%を占めています。重そうに見えたto_csv_rowは0.061秒で約1.2%にすぎません。cProfile自体の計測負荷を含む参考値ですが、ここをどれだけ速く書き直しても全体はほとんど縮まないことは読み取れます。これがクヌースの言う「重要でない部分」への努力です。

原因はnot inをlistに対して使っていることで、1件ごとに最大7,143要素を先頭から比べています。停止顧客をsetに替えると検索はハッシュ表の参照になり、出力行数は同じ17,134行のまま、5回の中央値で5.777秒から0.041秒、約140倍速くなりました。変更は1行で、可読性も落ちていません。

setが適しているのは、この例のように多数の整数IDを繰り返し検索し、並び順や重複を保つ必要がない場合です。要素が数個しかない、順序に意味がある、要素がハッシュ化できないといった条件ならlistのままで構いません。前の章の表で「計測前に決めてよい」としたのは、こうした条件を見て構造を選ぶ判断のことです。

Pythonの範囲で直せない箇所が残った場合に初めて、C拡張への置き換えのような重い手段を検討します。型宣言でどこまで速くなるかはCythonの実測で確かめられます。データベースが遅い場合はアプリ側のプロファイラではなくPostgreSQLの実行計画(EXPLAIN ANALYZE)で見ます。

この格言を持ち出すべきでない場面

「早すぎる最適化は諸悪の根源」は、計測の手間を省くための免罪符として使われると害になります。次の4つの場面では、この格言を理由に性能の議論を打ち切るべきではありません。

  • 計算量の悪い書き方を正当化するとき:上の例のように、数千件のIDに対して繰り返しlistで所属判定をする書き方は「小さな効率化」の対象ではなく、データ構造の選択ミスです。
  • 応答時間に上限がある処理:60fpsのゲームなら1フレームに使える時間は約16.7ミリ秒で、超えた分だけ目標のフレームレートを維持できなくなります。
  • 後から変えにくい決定:公開API・DBスキーマ・保存形式は、性能を理由に後で変えると互換性の維持やデータ移行の対応が必要になります。
  • 計測せずに「早すぎる」と退けるとき:格言は計測を前提にしています。測らずに最適化を却下するのも、測らずに最適化するのと同じ推測です。

逆に、まだ動いてもいないコードでビット演算やループ展開を議論している場面こそ、この格言の出番です。まず読みやすく正しいコードを書き、リーダブルコードの原則で保守しやすく保ったうえで、計測で3%を探します。

よくある質問

「早すぎる最適化は諸悪の根源」は誰の言葉ですか?

ドナルド・クヌースです。1974年12月の論文「Structured Programming with go to Statements」(ACM Computing Surveys 6巻4号、268ページ)と、同じ月に掲載されたチューリング賞講演「Computer Programming as an Art」に出てきます。

premature optimizationとはどういう意味ですか?

どこが本当に重要かを計測で特定する前に行う最適化のことです。クヌースは、重要でない部分の速度に時間を使うとデバッグと保守に強い悪影響が出ると書いています。

「early optimization is the root of all evil」と同じ意味ですか?

趣旨は同じですが、原文の語はprematureです。「early」は時期の早さだけを表し、「重要な箇所が特定される前」という原文の含みは伝わりません。

原文の全文はどこで読めますか?

論文はACM Digital Library(DOI 10.1145/356635.356640)に収録されています。該当の段落は268ページで、「97%」と「3%」に挟まれた3文と、その後の「重要なコードが特定された後で」という文までが一続きです。

最適化はいつ始めればよいですか?

プロファイラなどでボトルネックを特定した後です。ただし検索に使うデータ構造や、ループ内でのDB問い合わせの有無のような設計上の判断は、計測を待たずに最初から適切に決めておきます。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.09 テックブログ ユニリタの不正アクセス:SaaS文書管理から漏れた顧客の認証情報と回す手順
  3. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  4. 2026.10.11 テックブログ GPT-6 Solとは?6.1 Solの変更点とLuna・Astraの使い分け【2026年10月】
  5. 2026.04.21 テックブログ Sarashinaとは?モデル一覧(TTS・OCR・Vision)と使い方・ライセンス・API提供形態

RELATED POSTS 関連記事

目次