プログラミング
Register deprivation: 強制的なレジスタ不足下でのスピルと実行時間
Register deprivation: spills and runtime under forced register scarcity (rjp.io)
要約
この研究では、gccの-ffixed-<reg>オプションを使用してレジスタを段階的に予約し、9つの小さなカーネルのスピルと実行時間を測定しました。レジスタの予約はスピルを確実に増加させますが、静的なスピル数は実行時間のコストを弱く予測するだけで、あるカーネルはスピルが23個増加してもスローダウンしませんでした。レジスタを減らすことは、ほとんどのカーネルの実行速度を14〜76%低下させましたが、SipHashカーネルは影響を受けませんでした。スピルあたりのコストはカーネルや使用されるレジスタファイルによって大きく異なり、静的なスピル数だけでは実行時のパフォーマンスへの影響を正確に予測できないことが示されました。
全文翻訳
Register deprivation: 強制的なレジスタ不足下でのスピルと実行時間
Intel Xeon E-2236 @ 3.40GHz · gcc 15.2.0 · 2026-07-13
gcc -ffixed-<reg> を使用してレジスタを段階的に予約し、9つの小さなカーネルをコンパイルした後、1台のマシンでスピルと実行時間を測定しました。レジスタの予約は確実にスピルを増加させますが、静的なスピル数は実行時間のコストの弱い予測因子であり、1つのカーネルは23個のスピルを増やしても全く遅くなりませんでした。コードは https://github.com/rjpower/spillbench にあります。以下のすべての図の背後にある数値は results.json にあります。
TL;DR
レジスタを減らすと、9つのカーネルのうち8つが最も厳しい予算で14〜76%遅くなります。9番目のカーネル(SipHash)は変化しません。すべての予算で結果はビット同一であるため、予約は生成されたコードのみを変更し、計算は変更しません。
追加されたスピル命令は、スローダウンと弱く相関します(105点でのピアソンr = 0.55)。1つの追加されたスピルのコストは、約0%/スピル(SipHash)から2.2%/スピル(FIRフィルター)の範囲であり、約30倍のばらつきがあります。効果は、カーネルが使用するレジスタファイルに固有です。SHA-256でXMMレジスタを予約すると+5.6%のコストがかかります。GPレジスタを予約すると+33%のコストがかかります。倍精度浮動小数点数行列乗算でGPレジスタを予約しても変化はありません。XMMを予約すると+34%になります。GCCは-O2でSSEを使用してSHA-256の一部を自動ベクトル化するため、XMMの予約はその整数カーネルに対して不活性ではありません。そのスタックスピル数は8→65に増加しますが、実行時間は+5.6%しか動きません。
測定は、gcc 15.2.0 -O2、Intel Xeon E-2236、ウォールクロック、1つの固定コアで15回の繰り返し最小値で行われました。
セットアップ
3つの仮説をテストします。
H1: レジスタが少ないほど、レジスタの数に単調にスピルが増加し、コードが遅くなる。
H2: スローダウンは、カーネルが実際に使用するレジスタファイル(整数カーネルはGPレジスタ、浮動小数点カーネルはXMM)に固有である。
H3: 静的なスピル数が実行時間のコストを予測する。
gcc -ffixed-<reg> を使用してレジスタを予約します。これは、コンパイル全体でアロケータのプールからレジスタを削除します。各カーネルを予算ごとに1回再コンパイルし、パスごとに1つの予約を追加します。
GPカーネルは、r15、r14、r13、r12、rbp、rbx、r11、r10、r9、r8の順に(15個のアロケイト可能なレジスタが5個まで減少。rspは決してアロケイト可能ではありません)。XMMカーネルは、xmm15からxmm4まで(16個から4個)を解放します。引数渡しやmul、可変シフトなどの命令に必要なABIレジスタ(rax、rcx、rdx、rsi、rdi、xmm0-3)は決して予約しません。6つの整数カーネルがGPファイルを、3つの浮動小数点カーネルがXMMファイルを処理します。
それぞれが既知の答えを持つ単一のC関数です。
カーネルファイル 何であるか 正しさのチェック
ChaCha20 GP 20ラウンドのストリーム暗号ブロック、16×32ビット状態 RFC 8439 §2.3.2 keystream
SHA-256 GP 圧縮関数、8変数+64ワードスケジュール SHA-256("abc")
SipHash-2-4 GP キー付きハッシュ、4×64ビットARX 参照ベクトル(15バイトメッセージ)
整数行列乗算 GP 4×4レジスタブロックint64マイクロカーネル ナイーブなトリプルループとの比較
LZ77 compress GP hashチェーンマッチファインダー 圧縮→解凍ラウンドトリップ
Quicksort GP インプレース再帰ソートint32 ソートされた出力
倍精度浮動小数点数行列乗算 XMM 4×4レジスタブロックf64マイクロカーネル ナイーブなトリプルループとの比較
FIRフィルター XMM 16タップフィルター、8出力レーン ナイーブなタップ合計との比較
Mandelbrot XMM エスケープ時間、ピクセルあたりのライブ倍精度浮動小数点数が少ない 既知のポイントでのエスケープカウント
スピルメトリックについては、ホット関数を逆アセンブル(objdump)し、インデックス付き配列アクセスを除く、固定スタックスロット(…(%rsp))を参照する命令をカウントします。これはスピル/リロードトラフィックの静的な代理です。動的なスピルカウントはありません。この環境ではハードウェアパフォーマンスカウンターが無効になっています(perf_event_paranoid=4)。タイミングについては、Rustハーネスが各ビルドをdlopenし、既知の答えテストを実行し、単一の固定コアで15回の繰り返しで最小値を取り、モノトニッククロックでワークロード全体をタイミングします。各カーネルのワークロードは約100msにサイズ設定されています。
マシン: Intel Xeon E-2236 @ 3.40 GHz、gcc 15.2.0、-O2 -fPIC -shared。
すべてのビルドは既知の答えテストに合格し、すべてのカーネルはすべての予算で同じチェックサムを返します。予約はここでは純粋なアロケーション制約です。結果を変更することはありませんでした。
結果1: レジスタの削除はほとんどのカーネルを遅くするが、すべてではない
レジスタが削除されると、9つのカーネルのうち8つが遅くなります。SipHash-2-4は遅くなりません。
図1. レジスタ予約数に対する実行時間、整数および浮動小数点カーネル
図2. レジスタ予約数に対するスピル数
図3. 最も厳しいレジスタ予算でのスローダウン
カーネルファイル 実行時間 フル→最小(ms) スローダウン スピル フル→最小
FIRフィルター XMM 135.5 → 238.9 +76% 8 → 43
整数行列乗算 GP 140.5 → 199.7 +42% 60 → 82
倍精度浮動小数点数行列乗算 XMM 131.5 → 176.4 +34% 66 → 100
SHA-256 GP 89.4 → 119.0 +33% 8 → 80
Quicksort GP 134.2 → 171.7 +28% 4 → 49
ChaCha20 GP 88.5 → 110.0 +24% 55 → 121
LZ77 compress GP 123.2 → 146.8 +19% 39 → 115
Mandelbrot XMM 101.6 → 116.2 +14% 0 → 7
SipHash-2-4 GP 79.4 → 78.1 -2% 0 → 23
すべての9つのカーネルでスピル数が増加します。8つで実行時間が増加します。H1は方向性では正しいですが、形状では正しくありません。どちらの曲線も予算に対してきれいに単調ではありません。静的なスピルは、いくつかのケース(ChaCha20とQuicksortはそれぞれ1ステップ)でレジスタが削除されると減少します。また、実行時間曲線は測定ノイズ内で局所的な低下を示します(LZ77は予算が低下するにつれて0.5ms以上減少する5つのステップ、SipHashは3つ、Quicksortは3つ)。アロケータは単調ではなく、実行時間信号は最小15回のタイミングでも約1%レベルでノイズが多いです。整数行列乗算は、フルレジスタファイルで60スピルから始まります。その4×4ブロックは15個のアロケイト可能なGPレジスタよりも多い16個のint64アキュムレータを保持するため、何かを取り除く前にスピルが発生します。
結果2: スピル数はスローダウンを予測しない
追加されたスピル数は、スローダウンのほとんどを説明しません。図4. 全てのカーネル・予算ポイントに対する追加スピル数とスローダウンの比較
全105(カーネル、予算)ポイント全体で、追加スピル数とスローダウンの間のピアソンrは0.55です。スピルあたりのカーネルコストは、約30倍異なります。
カーネル 最小での追加スピル数 スローダウン スピルあたりのスローダウン
FIRフィルター +35 +76% 2.18 %/スピル
Mandelbrot +7 +14% 2.05 %/スピル
整数行列乗算 +22 +42% 1.92 %/スピル
倍精度浮動小数点数行列乗算 +34 +34% 1.00 %/スピル
Quicksort +45 +28% 0.62 %/スピル
SHA-256 +72 +33% 0.46 %/スピル
ChaCha20 +66 +24% 0.37 %/スピル
LZ77 compress +76 +19% 0.25 %/スピル
SipHash-2-4 +23 -2% ≈0 %/スピル
整数行列乗算は、わずか22個の追加スピルで+42%に達します。LZ77は76個のスピルを追加して+19%になります。SipHashは23個のスピルを追加しても、測定可能なスローダウンはありません。スタックはL1に常駐するため、クリティカルな依存パス上にないスピルはほぼ無料ですが、タイトな再帰(ARXチェーン、アキュムレータ更新)内のスピルはリロードレイテンシで直列化されると推測します。静的なカウントは、この2つを区別しません。
結果3: スローダウンはレジスタファイルに固有である
カーネルが使用しないレジスタファイルを予約することは、使用するファイルを予約するよりもはるかに安価です。図5. 間違ったレジスタファイルを予約しても実行時間はほとんど変化しない
2つのカーネルを反対のレジスタファイルに対して再実行しました。倍精度浮動小数点数行列乗算でGPレジスタを予約すると変化はありません(0→10予約で131.9 → 131.1 ms)。同じカーネルでXMMを予約すると+34%のコストがかかります。これはクリーンなケースです。浮動小数点演算はXMMバウンドであり、GPレジスタを十分に持っています。SHA-256は厄介なケースです。XMMの予約は+5.6%のコストがかかりますが、GPの場合は+33%です。しかし、XMMの予約は不活性ではありません。SHA-256のスタックスピル数は8→65に増加します。そのホット関数は、フルバジェットで155個のXMM参照命令を含んでいます。これは、GCCが-O2でSSEを使用してメッセージスケジュールのバイトスワップとワード計算を自動ベクトル化するためです。XMMを予約すると、その処理はGPレジスタとスタックに戻されます。これはスタックスピルとして現れますが、スケジュールがクリティカルパスではないため、実行時間にはほとんど影響しません。したがって、H2は実行時間に対して有効ですが、「カーネルが使用するファイル」がクリーンではないという注意点があります。GCCは整数コード内でXMMを機会的に使用します。
驚いたこと
スピル数は実行時間の弱い予測因子です(r = 0.55、スピルあたりのコストに30倍のばらつき)。スピル数は通常、レジスタプレッシャーストーリーで語られるものなので、より緊密な関係を期待していました。SipHashは、実行時間の-2%の変化に対して23個のスタックスピルを増加させます。クリティカルパスから外れたスピル、L1常駐スタックへのスピルは、ここではほぼ無料です。