11 人が閲覧(直近 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%の機会は逃すな」と続きます。

この記事では、原文の前後と「3%」の根拠、同じ年のチューリング賞講演との違い、誤読されやすい点を原典で確認し、「早すぎるかどうか」を計測で判断する手順を、2026年10月に出たPython 3.15のプロファイラでの実測値つきで解説します。

まとめ:原文が求めているのは「計測してから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%の機会は逃すなと言っています。
  • 3%の根拠:前のページで、I/O待ちの少ないプログラムでは実行時間の大半がソースの約3%に集中するという経験則を、計測の研究を挙げて示しています。I/O待ちが中心のプログラムには、そのままは当てはまりません。
  • 「早すぎる」の基準:どこが重要かを計測で特定する前、という意味です。本記事の例では処理時間の約98%が1つの関数に集中しており、listをsetに替えるだけで2.525秒が0.024秒(Python 3.15.0、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.)。そのうえで、計測ツールを使ってきたプログラマーの経験では直感による推測は外れるとし、7年間そうしたツールを使ってきた結果、今後のコンパイラはどこにコストがかかっているかを全プログラマーに示すべきだと確信した、と書いています。格言だけを切り出すと、この「計測で特定した後なら積極的に手を入れる」という後半が抜け落ちます。

3%の根拠:I/O待ちの少ないプログラムでの経験則

「3%」は思いつきの比率ではありません。クヌースは1ページ前の267ページで、探索ループの書き換えで実行時間が約6対4に縮んだ例を示した直後に、次のように書いています。

Experience has shown (see [46], [51]) that most of the running time in non-IO-bound programs is concentrated in about 3 % of the source text.

「経験上、I/Oに縛られないプログラムでは、実行時間の大半がソースコードの約3%に集中している」という意味です。根拠に挙げた[46]はダン・インガルス(Dan Ingalls)の「The execution time profile as a programming tool」(1972年)、[51]はクヌース自身の「An empirical study of FORTRAN programs」(Software: Practice and Experience 1巻2号、1971年)で、どちらも実際のプログラムの実行時間を測った研究です。続けて、短い内側のループが全体の速度を左右することが多く、そのループを10%速くすれば全体もほぼ10%速くなる、と述べています。

268ページの「say about 97%」は「まあ、およそ」と幅を持たせた言い方ですが、背景には計測の裏付けがあります。一方で、対象はnon-IO-bound(I/O待ちの少ない)プログラムと限定されています。データベースやAPIの応答を待つ時間が大半を占めるWebアプリケーションでは、CPUを使っている3%を探すだけでは足りません。これは後の章で実測します。

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

検索では「early optimization is the root of all evil」「pre-optimization」という形も使われていますが、原文の語はprematureです。単に時期が早いという意味ではなく「機が熟す前の」という含みがあり、この論文では「重要な箇所が特定される前」を指します。日本語では「早すぎる最適化」のほか、Wikipedia日本語版の「最適化(情報工学)」のように「時期尚早な最適化」「時を得ない最適化」と訳す例もあります。

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

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

クヌースは1974年に、同じ趣旨の文を2つの文献に書いています。引用元によって文言が違うのはこのためです。論文の中にも、言い回しを変えて同じ主張をした箇所がもう1つあります。

文献・箇所 掲載 該当の表現
Structured Programming with go to Statements ACM Computing Surveys 6巻4号 p.268 97%と3%の文脈で登場
同論文「Efficiency」節 同 p.294 premature emphasis on efficiency
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%の改善は決して小さいとはみなされない。ソフトウェア工学でも同じ見方をすべきだ」という意味です。直前では、小さな効率を無視せよという当時の通説を、「最適化した」プログラムをデバッグも保守もできない人たちへの過剰反応だと評しています。クヌースが否定したのは効率化ではなく、重要でない箇所への見当違いの努力です。

「まず読みやすく書く」の原典での位置づけ

「最初はきれいなコードを書き、必要になってから最適化する」という進め方も、同じ論文が294ページの「Efficiency」節で説明しています。

In our previous discussion we concluded that premature emphasis on efficiency is a big mistake which may well be the source of most programming complexity and grief.

早い段階で効率を重視することは大きな誤りで、プログラミングの複雑さと苦労の大半の原因かもしれない、という主張です。クヌースはここで、効率が問題になる場合でも関係するのはコードのごく一部だとし、よく構造化されたプログラムから始めて、理解の済んだ変換を機械的に当てていけば、明快さを犠牲にした高速化をしても長く保守できるプログラムになると書いています。読みやすいコードは最適化の前提であり、最適化と対立するものではない、というのが原典の立場です。

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

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

「早すぎる最適化をしない」ことは「わざわざ遅い書き方を選んでよい」ことを意味しません。同じくらい単純に書ける効率のよい方法があるなら、最初からそちらを選びます。listの先頭から要素を取り出し続けるpop(0)が遅い理由や、後の章で扱うlistとsetの違いは、その典型です。

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

xkcdの1691話「Optimization」(2016年6月8日)の補足テキストは、早すぎる最適化は諸悪の根源だから、まず最適化が早すぎるかどうかを判定するシステムを作ろう、という冗談です。実際には判定のための仕組みは要りません。クヌースの言う「小さな効率化」と、設計段階で決めるべき性能上の判断を分ければ、線は引けます。後者まで「早すぎる最適化」として先送りすると、あとで直すコストが跳ね上がります。

判断の対象 計測前に決めてよいか
検索に使うデータ構造(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原則と同じ方向を向いています。

計測してから最適化する手順:Python 3.15のプロファイラで実測

パイクの同じ文書の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%に対応します。Pythonでは2026年10月9日に出た3.15で、プロファイラがprofilingパッケージにまとめられました(PEP 799)。関数呼び出しをすべて記録する従来型はprofiling.tracing(中身はcProfileと同じ)、新しく入った統計的サンプリング方式はprofiling.sampling(愛称Tachyon)です。純Python実装のprofileモジュールは3.15で非推奨になり、3.17で削除されます。

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

2万件の注文から、停止中の顧客(7,143件)を除いてCSVの行を作る処理を例にします。文字列の整形が重そうに見えますが、推測せずに測ります。cProfileはPython 3.14以前でもそのまま動き、3.15では1行目をimport profiling.tracing as 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)

# 置き換えても出力が変わらないことを先に確かめる
assert export(orders, blocked_list) == export(orders, set(blocked_list))

# 計測するのは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.15.0(macOS、Intel Core i9-9880H)で実行した出力の要部です。秒数は環境によって変わるので、手元では割合と順位を見てください。

         37138 function calls in 2.569 seconds

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    20000    2.502    0.000    2.502    0.000 prof_demo.py:20(is_allowed)
    17134    0.040    0.000    0.040    0.000 prof_demo.py:16(to_csv_row)
        1    0.025    0.025    2.568    2.568 prof_demo.py:24(export)

list: median 2.525s rows=17134
set: median 0.024s rows=17134

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

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

原因はnot inをlistに対して使っていることで、1件ごとに最大7,143要素を先頭から比べています。停止顧客をsetに替えると検索はハッシュ表の参照になり、出力行数は同じ17,134行のまま、5回の中央値で2.525秒から0.024秒、約100倍速くなりました。変更は1行で、可読性も落ちていません。クヌースが267ページで、内側のループの演算を減らすために「プログラムとデータ構造を変える」と書いたのと同じ種類の改善です。

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

コードを変えずに測るサンプリングプロファイラ(Tachyon)

cProfileは計測したい処理をコードに書き込むか、python -m cProfileで起動し直す必要があります。3.15のprofiling.samplingは、実行中のプロセスのスタックを外から一定間隔で読み取る方式で、公式ドキュメントはコードの変更も再起動も要らず、本番環境でも使えると説明しています。上のコードからimport cProfile・pstats・statistics・timeと、cProfile.run以降の計測部分を削り、最後をrows = export(orders, blocked_list)とprint(len(rows))の2行にしたsample_demo.pyを、10kHzでサンプリングしました。

# スクリプトを起動して測る
python -m profiling.sampling run -r 10khz --sort tottime -l 5 sample_demo.py

# 動いているプロセスに後から接続して3秒だけ測る
python -m profiling.sampling attach -d 3 --sort tottime -l 3 <PID>
Captured 19229 samples in 1.92 seconds
       nsamples   sample%   tottime (s)    cumul%   cumtime (s)  filename:lineno(function)
    18590/18590      98.0         1.859      98.0         1.859  sample_demo.py:17(is_allowed)
        169/169       0.9         0.017       0.9         0.017  sample_demo.py:13(to_csv_row)
       79/18840       0.4         0.008      99.3         1.884  sample_demo.py:21(export)

Python 3.15.1のLinuxコンテナでの結果で、cProfileと同じくis_allowedが98.0%を占めました。exportを5回繰り返して約10秒動く別のスクリプトを起動し、1秒後にattach -d 3で接続したときも、3,000サンプル中98.2%でした。秒数はサンプル数から求めた推定値で、ドキュメントは1,000サンプル程度だと5%と出た関数が実際には3〜7%の可能性があるとしています。正確な呼び出し回数が要るとき、1〜2%の差を比べるとき、1秒未満で終わるスクリプトではprofiling.tracingかtimeitを使うよう案内されています。

他プロセスのメモリを読むため権限が要ります。macOSではrootか専用のentitlementが必要で、一般ユーザーのままrunすると「Permission Error: Unable to access process memory on macOS」で止まりました。Linuxでは通常、root、CAP_SYS_PTRACE、Yamaのptrace_scopeの調整のいずれかが要ります。既定のptrace_scopeは1で、親プロセスからしか読めないため、attachで別のプロセスに接続するときに制限が効きます(今回のコンテナは--cap-add SYS_PTRACEで起動)。本番で継続的に測る仕組みはContinuous Profilingと呼ばれます。

I/O待ちが支配的なプログラムの計測:wallとcpuの違い

クヌースが3%の経験則をnon-IO-boundのプログラムに限ったことは、今のアプリケーションでは無視できません。注文1件ごとに顧客情報を問い合わせる処理を、2ミリ秒の待ち(time.sleep)で模して測ります。

import time


def fetch_customer(customer_id):
    time.sleep(0.002)  # DBやAPIの応答待ちの代わり(2ミリ秒)
    return {"id": customer_id}


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


def export(order_ids):
    rows = []
    for oid in order_ids:
        customer = fetch_customer(oid % 500)  # 1件ごとに問い合わせる(N+1)
        rows.append(to_csv_row(oid, customer))
    return rows


print(len(export(range(1_000))))

profiling.samplingの--modeを既定のwall(待ち時間も含めた経過時間)とcpu(CPUを使っている時間だけ)で切り替えた結果です。

関数 wall(経過時間) cpu(CPU時間)
fetch_customer 2.201秒(99.6%) 0.047秒
to_csv_row 0.003秒(0.1%) 0.006秒

wallとcpuは別々に実行した結果で、どちらもサンプル数からの推定値ですが、経過時間約2.2秒に対してCPUを使っていた時間は0.06秒足らずでした。CPU時間だけを見て文字列整形を速くしても、利用者が待つ時間は縮みません。この処理で直すべきは、1,000回の問い合わせを顧客IDごとにまとめて取る設計で、これは判断基準の表で「計測前に決めてよい」とした項目です。いわゆるN+1問題で、データベース側の遅さはPostgreSQLの実行計画(EXPLAIN ANALYZE)のように、アプリのプロファイラとは別の道具で見ます。利用者が待つ時間を問題にしているならwall、計算の重さを探すならcpuと、測る前に何を縮めたいかを決めてください。

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

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

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

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

よくある質問

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

ドナルド・クヌースです。1974年12月の論文「Structured Programming with go to Statements」(ACM Computing Surveys 6巻4号、268ページ)と、同じ月に掲載されたチューリング賞講演「Computer Programming as an Art」に出てきます。1989年にクヌースが「Hoare’s dictum」と書いたため、ホーアの言葉として紹介されることもあります。

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

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

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

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

「root of all evil」はどういう意味ですか?

「諸悪の根源」です。欽定訳聖書のテモテへの手紙一6章10節「For the love of money is the root of all evil」で知られる定型句で、クヌースは講演版では「(or at least most of it)」と付けて「少なくとも悪の大半」と範囲を緩めています。

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

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

関連記事

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

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

資料請求

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

  1. 2025.02.27 コラム トラッキングビーコン(Webビーコン)とは?仕組み・用途・確認と対策を徹底解説
  2. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】
  5. 2026.10.21 テックブログ ユミルリンク不正アクセス第二報で延べ5,162万件:Cuenote FC利用企業と受信者の確認点

RELATED POSTS 関連記事

目次