コードに一行余分に書き加えたら、プログラムは遅くなるどころか4倍速くなった。これは都市伝説のように聞こえるが、2026年7月12日、purplesyringa というプログラマーがブログで、自ら実証したこの出来事を記録した。
彼が当時書いていたのはデータ圧縮プログラムだ。中にごく短いループがあり、核心のコードは一行だけ——テーブルから次の値を繰り返し引いてきて、引いてきたものを保存する。その一言だけ、すっきりとして無駄がない。だがプログラムの動作はひどく遅く、見ていてやきもきする。彼はあらゆる定石的な最適化を試したが、効果は芳しくなかった。最後に彼がやったのは、自分でも馬鹿げていると思うようなことだった:一見完全に不要な if 判定を一つ加えたのだ——「新しく引いてきた値と現在の値が同じかどうか」を判定し、違えば更新、同じなら飛ばす、と。
この if の「無駄口」の程度は、こう言えば分かるだろう:君がポケットに百円あると知っているのに、それでも手を突っ込んで触れ、本当にそこにあると確認してから出かけるようなものだ。加えようが加えまいが、ポケットの中は百円のままだ。だが不思議なことに、加えた後、プログラムは320マイクロ秒で走っていたのが80マイクロ秒になり、まるまる4倍速くなった。
筆者がこの事例を初めて読んだとき、やはり冗談のように思えた。だがこれはブラックマジックではない。その裏には、現代のコンピュータがどうやって答えを「当てる」かという物語が隠されている。
工場の組立ラインにおけるボトルネック
このことを理解するには、まず CPU がどう働くかを知る必要がある。
CPU を工場の組立ラインに例えてみよう。ラインの作業員は、前の製品が完全に組み上がるのを待ってから次に取り掛かることはしない——それでは遅すぎる。彼らは作業を多くの小さなステップに分解する:切断、研磨、塗装、検品……各ステーションが異なる製品を同時に処理する。そうすれば、ライン全体の生産速度は「最も遅いステーション」に依存することになり、「一つ作り終えてから次を」ではなくなる。これが現代 CPU の「命令レベルの並列性」——複数の命令を同時に処理し、効率を大きく高める仕組みだ。
だが組立ラインには致命的な弱点がある:次の製品が何かが、前の製品の加工が完了してみないと分からない場合、ライン全体が詰まってしまう。作業員はただ待つしかない。
purplesyringa のコードでは、状況はまさにそうだった。彼のループはこうだ:j = next_j[i][j]——現在の値 j でテーブルを引き、次の j を得て、その新しい j を次のラウンドの引きに回す。各ラウンドが前のラウンドの結果に依存している。CPU のライン作業員たちは上流の出荷をやきもきしながら待ち、上流もまたさらに上流を待つ……ライン全体が一方通行の渋滞列車になってしまう。これがいわゆる「データ依存チェーン」がもたらす遅延のボトルネックだ。
「道順を予測」するナビゲーションシステム
だが現代 CPU には、この局面をちょうど対処できる一つの得意技がある。それを「分岐予測器(branch predictor)」と呼ぶ。
同じ工場の比喩を使おう:ライン上に検品ステーションがあり、作業員は検査結果に応じて製品を A 経路か B 経路かに振り分ける。もし毎回検品が終わるまで経路を選ばずに待っていたら、ラインはまた詰まる。そこで工場は「過去の経験システム」を導入する——この検品ステーションに出くわすたび、システムは過去99回の選択に基づいて予測する:今回もおそらく A だ、と。作業員は製品をあらかじめ A 経路へ押し進める。当てればラインは滑らかに止まらない。外せば、すでに A 経路へ押し進めた半製品を引き戻し、B 経路でやり直す。
CPU の分岐予測器こそがこのシステムだ。プログラムが各「分かれ道」で過去にどちらを選んだかを記録し、複雑な回路を用いて次の進行方向を予測する。現代 CPU の分岐予測の正解率は通常95%以上——大半の人間が意思決定するよりも高い。
purplesyringa の洞察はこうだ:彼のコードには明確な「分かれ道」(if-else)こそなかったが、データ依存チェーンそのものが一つの見えない「待ち」だった。彼はひらめいた:もし明示的な分かれ道を加えて、分岐予測器を介入させたらどうなるか、と。
あの「無駄話」の本当の役割
彼が加えた if は、こういうロジックだ:テーブル引きの結果と現在の値が異なるかを判定し、同じなら何もせず、異なれば更新する。大半の場合、引いてきた値と現在の値は実際に同じなので、CPU の分岐予測器はすぐに「この if の本体はほとんど実行されない」と「学習」する。
そこで CPU は大胆に推測する:次のラウンドでも if の本体は飛ばされる、と。飛ばされると推測すれば、前のラウンドの結果を待つ必要がない——j は変わらないと仮定して、そのまま先へ走る。ラインが再び動き出す。複数ラウンドのループを並列処理できる。
まれに引き結果が本当に異なる時、分岐予測器は自分の予測が外れたと気づき、すでに間違った道を進んだ半製品を捨て、正しい j の値でそのラウンドをやり直す。この過程を「分岐予測ミスペナルティ」と呼ぶ。だが予測を外す割合は極めて低いため、この代償は、ずっと棒立ちで待つ代償に比べればるかに小さい。
結果として:一見完全に無駄な if 文が、分岐予測器に「当ててよい」というシグナルを与えたのだ。それは、もともと直列でしか実行できなかった依存チェーンを、投機的に並列実行できるラインへと変えた。
コンパイラの「親切」が招いた弊害
物語はここで半分しか語られていない。もっと厄介な相手がいる:コンパイラだ。
コンパイラとは、プログラマーが書いた人間が読めるコードを、CPU が実行できる機械語命令に翻訳するプログラムだ。現代のコンパイラは非常に賢い——賢すぎて、自ら「無駄なコード」を認識してそのまま削り落とす。コンパイラの目には、purplesyringa が加えた if は「A が A と等しくない場合にのみ A を更新する」と言っているように見え、それは当然無駄口だ。コンパイラは冷笑し、それを最適化して消してしまう。
プログラマーは CPU の分岐予測器を騙そうとしたが、コンパイラの方が先に、その騙しの小道具を没収してしまった。
これが記事のタイトルにある「保守的な判断」の意味だ——そして筆者がこの事例で最も味わい深いと感じる点だ:コンパイラは「プログラムの意味を変えない」という原則を厳格に守る——君が書いたものが論理的に無駄なら、翻訳してやらない。しかしコンパイラの知らないことに、ある種のコードの真の価値はハードウェアの層に隠されている:それは CPU に、投機的に実行してよいというシグナルを与えるのだ。
これは実のところ三者の駆け引きだ。CPU は攻撃的だ:必死に当て、どうにかして仕事を前倒しにする。コンパイラは保守的だ:意味を厳格に守り、多くも少なくもしない。そしてプログラマーはその間に立ち、CPU の攻撃性を利用したい半面、コンパイラの保守性を欺かねばならない。
「ここには触るな」の封印
purplesyringa が行き着いた方法は、C 言語の volatile というキーワードを使うことだった。この語は C 言語において、コンパイラに「ここには触るな」と貼る封印のようなものだ——コンパイラに、このデータは君の知らないうちに変わるかもしれないので最適化するな、毎回きちんと読め、と告げる。
この封印を貼れば、コンパイラは if の条件が「永遠に成立しない」無駄口だとは見なさなくなり、それを残す。if が残れば、分岐予測器が当てるものを持つ。ラインは再び並列で回り出す。
後に Lobsters コミュニティの議論で、別のプログラマー ibookstein が、C++20 の [[unlikely]] 属性(コンパイラに「この分岐はめったに通らない」と明示的に伝える)でも同様の効果が得られると発見した。ただし purplesyringa は、volatile 封印の方式の方が生成される機械語の質が良く、特定のコンパイラに依存しない、と指摘している。
より大きな概念:値投機(value speculation)
Lobsters のスレッドで、ある人がこの技巧には正式な名前があると指摘した——「値投機(value speculation)」だ。核心の考え方はこうだ:ある値の取り得る値について「高い確率で当てられる」という経験的方針があるとき、分岐予測器を利用して投機的に実行することで、データ依存チェーンを打破できる。
この概念はさらに古い研究やブログ(Paul Khuong、Per Vognsen らの仕事)にさかのぼれる。mazzo.li の古典的な記事では、同じ技巧が連結リストの走査の高速化に使われている:連結リストを走査するとき、次のノードのアドレスは現在のノードが持つポインタに依存する——これもまたデータ依存チェーンだ。だが「次のノードはメモリ上で現在のノードのすぐ隣にある」と推測できれば、CPU に先取りしてプリフェッチさせ、スループットを14GB/s から45GB/s へ引き上げられる(データが CPU キャッシュ内にある場合)。
purplesyringa の if 技巧と値投機は本質的に同じことだ:安価な推測で、高価な待ちを置き換えるのだ。
何があなたに逆らうのか
この件で最も面白い点は、三層の「思い込み vs 実際」の衝突を暴き出していることだ:
第一層、人間の直感は「コードは少ない方が速く走る」と考える。だがこの事例では、一行余計に書いた方が速くなった——なぜならその行の機能は計算ではなくシグナル送信だからだ。
第二層、コンパイラは「論理的に無駄なコードは削るべき」と考える。だがある種のコードの効用は、論理的意味の層ではなく、ハードウェアの挙動の層に隠されている。
第三層、私たちは通常「当て外しには代償が伴うから、当てるべきではない」と考える。だが現代 CPU の設計哲学はまさに逆だ:大胆に当て、当てれば得、外してもやり直せばよい。当てる確率が十分に高ければ、総体として得になる。
この物語に、大げさな結末はない。それは単に、あるプログラマーが圧縮アルゴリズムを最適化しているとき、偶然出くわした反直観的な事実に過ぎない。だがこの小さな if 文を通して、現代コンピュータの最下層にある精妙な真実が見える:CPU は賭博師であり、コンパイラは弁護士であり、そして最も優れたプログラマーとは、いつ弁護士を欺き、その情報を賭博師へ渡すべきかを心得ている者のことだ。
参考ソース
- Purplesyringa ブログ:Quadrupling code performance with a “useless” if(2026年7月12日、原文は完全な技術詳細、コード例、性能データを記録)
- Lobsters コミュニティ議論(s/1an425):104ポイント、14コメント。ibookstein の
[[unlikely]]代替案や、mikejsavage が指摘した「値投機」概念のリンクを含む - mazzo.li:Beating the L1 cache with value speculation(2021年7月、連結リスト走査への値投機技術の応用を詳述、性能比較図付き)