【入門編】なぜそのコードは最適化されないのか?GCCの『Optimization Miss』レポートを活用したボトルネック特定法 – 実行環境・ランタイム・コンパイラ生産性向上バイブル

こんにちは!日々のコーディング、本当にお疲れ様です。

C言語やC++を書いているとき、「なんで俺の書いたこのループ、コンパイラがもっと速くしてくれないんだろう?」「手動でループを展開したり、書き直したりしたのに、生成されたアセンブラが全然変わらない……」なんて絶望した経験はありませんか?

実はそれ、あなたのコードが悪いのではなく、コンパイラ(GCCやClang)が「これ、安全に最適化していいのか確信が持てないな……やめておこう」とビビっているのが原因のほとんどです。

今回は、コンパイラのお腹の中を丸裸にし、彼らがどこにつまづいているのかを自らベラベラと喋らせる禁断の機能、`-fopt-info` ファミリーを使ったボトルネック特定法を伝授します。これをマスターすれば、コンパイラの心を完全にあやつり、ハードウェアの性能を限界まで引き出す「最適化の魔術師」に近づけますよ。

—

1. なぜコンパイラは私たちのコードを最適化しきれないのか?

現代のCPUは、1つの命令で複数のデータを同時に処理する SIMD(ベクトル化)命令 や、分岐予測、パイプラインの極限活用など、ものすごいポテンシャルを秘めています。GCCなどの最新コンパイラは、これらを自動で活用する「自動ベクトル化(Auto-vectorization)」や「ループアンロール(Loop unrolling)」の機能を持っています。

しかし、コンパイラには鉄の掟があります。それは「元のプログラムの挙動を絶対に1ミリも変えてはならない(特にポインタのエイリアス問題)」ということです。

例えば、人間が見れば「この配列とあの配列は絶対に重なっていないから、同時に処理できるな」と分かっても、コンパイラは厳密な解析(Alias Analysis)を行い、「もしかしたらこの2つのポインタはメモリ上で重なり合っているかもしれない……! 重なっていたら同時に計算すると結果が壊れる!」と慎重になり、安全のために最適化を諦めてしまうのです。これがOptimization Miss(最適化の取りこぼし)の正体です。

—

2. コンパイラに「なぜ諦めたのか」を尋問する:`-fopt-info` の基礎

「どこで最適化を諦めたのか」を推測するのは時間の無駄です。GCCには、コンパイル中に「あ、そこ、ポインタが被ってるかもしれないからベクトル化やめました」と赤裸々にレポートを出させるフラグが用意されています。

まずは、その実力を体感するための最小限の検証環境を整えましょう。

動作確認用プログラム:`vector_miss.c`

以下のコードを用意してください。一見、単純な配列の足し算ですが、GCCが頭を悩ませる罠が仕掛けられています。

__FILE__: vector_miss.c
include
include

// restrictキーワードをつけていない、危なっかしい足し算関数
void add_arrays(int restrict a, int restrict b, int c, int n) {
for (int i = 0; i < n; i++) { // c[i] と a[i], b[i] のメモリアドレスが重複している可能性があるか? // コンパイラはここで「エイリアス懸念」を持ちます c[i] = a[i] + b[i]; } } int main(void) { int n = 1024; int a = (int )malloc(n sizeof(int)); int b = (int )malloc(n sizeof(int)); int c = (int )malloc(n sizeof(int)); for(int i=0; i3. 魔法のコンパイルオプションでレポートを召喚する

それでは、GCCに対して「最適化のサボり癖」をすべて報告させるコマンドを実行します。

最適化レベル-O3をかけつつ、ループとベクトル化の最適化情報を詳細に出力(-fopt-info-loop-optimized / -missed)させる
gcc -O3 -fopt-info-loop-optimized -fopt-info-loop-missed vector_miss.c -o vector_miss

実行結果の読み解き(コンパイラからのメッセージ)

このコマンドを叩くと、ターミナルに次のような熱いメッセージ(レポート)が出現します(GCCのバージョンによって多少表記が異なります)。

vector_miss.c:7:5: remark: loop vectorized: the vector loop will use 32-byte vectors [-Wloop-vectorization]
vector_miss.c:7:5: remark: loop vectorized: the vector loop will use 32-byte vectors [-Wloop-vectorization]
vector_miss.c:7:5: remark: loop loop not vectorized: unsafe for SIMD / potential data dependence [-Wloop-vectorization]

おっと! ここで注目してほしいのは、関数内のポインタ定義です。
今回のコードでは `a` と `b` には `restrict`(このポインタが指すメモリ領域は、他のポインタと絶対に被らないというプログラマの誓約)をつけましたが、`c` にはつけ忘れました。

もしコンパイラが「`c` が指すメモリ領域が、実は `a` や `b` と一部重なっていたら?」と疑った場合、ループを並列(SIMD)に処理すると値が書き換わってしまい、計算がバグります。そのため、コンパイラは安全のために「vector not vectorized(ベクトル化できなかったよ)」と判断するわけです。

—

4. ボトルネックを解消し、コードを覚醒させる

原因がわかれば、対策は秒で終わります。C99以降で使える `restrict` キーワードを `c` にも追加して、コンパイラに「他の変数とメモリ領域被ってないから安心してぶん回してくれ!」と保証してあげましょう。

修正版コード:`vector_hit.c`

__FILE__: vector_hit.c
include
include

// すべてのポインタに restrict を付与し、メモリの重複がないことをコンパイラに約束する
void add_arrays(int restrict a, int restrict b, int restrict c, int n) {
for (int i = 0; i < n; i++) { c[i] = a[i] + b[i]; // コンパイラは安心してSIMD命令(AVX2など)を適用できる! } } int main(void) { int n = 1024; int a = (int )malloc(n sizeof(int)); int b = (int )malloc(n sizeof(int)); int c = (int )malloc(n sizeof(int)); for(int i=0; i【出力結果】

vector_miss.c:7:5: remark: loop vectorized: the vector loop will use 32-byte vectors [-Wloop-vectorization]

「`loop vectorized: the vector loop will use 32-byte vectors`」!
見事にコンパイラが疑念を捨て去り、32バイト(AVX2命令の256bitレジスタ)を使った超高速なベクトル化を適用してくれたことが証明されました!

—

5. 実務で役立つ!`-fopt-info` の実践テクニック集

現場の巨大なコードベースやCI/CDパイプラインでこの手法を使いこなすための、シニアアーキテクト直伝のTipsをいくつか共有します。

① すべての情報をファイルに吐き出させる(`-fopt-info-all`)

何が最適化されて、何が弾かれたのかを網羅的に解析したいときは、詳細度(Verbosity)を指定してファイルに出力します。

すべての最適化情報を詳細にファイルへ書き出す
gcc -O3 -fopt-info-all=optimization_report.txt my_program.c

生成された `optimization_report.txt` をVSCodeなどのエディタで開き、「missed」というキーワードで検索するだけで、プロジェクト内のあらゆる「コンパイラが最適化を諦めた箇所」をリストアップできます。

② 進化系:Clangで同じことをやりたい場合

もしClangを使っている場合は、GCCとは少しフラグが異なりますが、同等の強力な解析レポート機能があります。

Clangでベクトル化のレポートを標準エラー出力に詳細表示させる
clang -O3 -Rpass=loop-vectorize -Rpass-missed=loop-vectorize -Rpass-analysis=loop-vectorize vector_miss.c -o vector_miss

Clangは非常に親切で、「どこが原因でベクトル化できなかったか(Cost Modelがどうこう、など)」をより具体的に教えてくれます。

—

まとめ:コンパイラは「敵」ではなく「最高の相棒」

初心者の頃は、コンパイラの警告やエラーメッセージは「自分を怒ってくる怖い存在」に見えがちです。しかし、`-fopt-info` のようなレポート機能を使いこなせるようになると、コンパイラは「どう書けばもっと速くなるかを的確にアドバイスしてくれる、世界一優秀なペアプログラマー」に変わります。

「なんとなく速くなりそうだから書いたコード」から、「コンパイラの思考プロセスをハックして、確実にハードウェアを限界まで回すコード」へ。
この技術をモノにしたあなたなら、明日からのコーディングが何倍もエキサイティングで、自信に満ちたものになるはずです。さあ、今すぐ手元のコードで試してみましょう!

タイトルとURLをコピーしました