実践の性能工学
この章でわかること:
- レイテンシを「分布」で語る方法 — 平均と中央値と最悪値は別の生き物 (実測: 中央値70ナノ秒、最大33ミリ秒)
- 計測を歪める協調的省略(coordinated omission)という罠
- 性能の回帰を防ぐ運用 — ベンチマークのCI化と安定化
- 本書全体の知識の地図 — 28章を貫く5つの原則
- なぜ体系に必須か — 知識は運用されて初めて性能になります。 個人の技能を、チームと時間に耐える仕組みへ変換するのが最終章です
レイテンシは分布で語る
Section titled “レイテンシは分布で語る”Webアプリケーションの性能は、ほとんどの場合レイテンシ (2章で導入した言葉の、 サービス応答時間への適用)で語られます。そして最初に 捨てるべき習慣が「平均で語る」ことです。
実験します。HashMapへの100万件の挿入を、1回ずつ個別に
計測して分布を見ます。
use std::collections::HashMap;use std::time::Instant;
fn main() { // HashMapへの100万件のinsertを「1回ずつ」計測する let n = 1_000_000u64; let mut map: HashMap<u64, u64> = HashMap::new(); let mut lat_ns: Vec<u64> = Vec::with_capacity(n as usize);
for i in 0..n { let start = Instant::now(); map.insert(i, i); lat_ns.push(start.elapsed().as_nanos() as u64); }
lat_ns.sort_unstable(); let pick = |p: f64| lat_ns[((n as f64 - 1.0) * p) as usize]; let mean = lat_ns.iter().sum::<u64>() as f64 / n as f64; println!("平均 : {mean:8.0} ns"); println!("中央値 : {:8} ns", pick(0.50)); println!("p99 : {:8} ns", pick(0.99)); println!("p99.9 : {:8} ns", pick(0.999)); println!("最大 : {:8} ns ← 外れ値(主因はリハッシュ=全件コピー)", lat_ns[n as usize - 1]);}筆者の実測(Playground)です。
平均 : 152 ns中央値 : 70 nsp99 : 260 nsp99.9 : 470 ns最大 : 33340370 ns (33.3ミリ秒)中央値は70ナノ秒。しかし最大値は33ミリ秒——中央値の47万倍です。 正体はもうおわかりでしょう。22章で 学んだリハッシュ(表の作り直し)の瞬間です。「償却すれば安い」 (7章)操作は、分布の裾を分厚くする—— 平均152ナノ秒という数字は、この山を完全に隠しています。
だから実務ではパーセンタイル(percentile)で語ります。 p50(中央値)、p99(100回に1回の遅さ)、p99.9。1回の操作の裏で m個のリクエストが走るなら、どれかがp99を踏む確率は1−0.99^mです ——m=50で約4割、数百なら大半のユーザーが毎回踏みます。 ファンアウトの大きな現代のシステムでSLO(サービスレベル目標)が p99系で書かれるのはこのためです。
ばらつきの源泉 — 本書の総復習
Section titled “ばらつきの源泉 — 本書の総復習”「たまに遅い」の犯人捜しは、本書で学んだ機構の総復習になります。 p99の異常を見たら、この一覧を上から疑ってください。
| 源泉 | 章 |
|---|---|
リハッシュ・Vecの再確保 |
7章・22章 |
大きな構造のdrop(解放の連鎖) |
18章 |
| アロケータの遅い経路・断片化 | 18章 |
| ページフォールト(初回タッチ、起動直後) | 14章 |
| asyncランタイムのブロッキング混入 | 19章 |
| ロック競合・false sharing | 5章・17章 |
| CPU周波数の変動・サーマルスロットリング | 21章・26章 |
| キャッシュ・TLBの冷え(コンテキストスイッチ後) | 27章 |
クラウド環境ではさらに2つ加わります。多くのインスタンスでは vCPUがSMTの論理コア(5章)であり、 物理コア換算で半分の実力しかない場合があること(インスタンス 仕様の確認が必要です)。そして隣人問題(noisy neighbor)——同じ物理 ホストの他テナントとキャッシュ・メモリ帯域(2章)を 取り合うため、同じコードの性能が日によって変わることです。 本書のPlayground実測がばらついていたのは、まさにこれでした。
性能の回帰を防ぐ — ベンチマークの運用
Section titled “性能の回帰を防ぐ — ベンチマークの運用”性能は、機能と同じように回帰します。誰かの無害なリファクタが インライン化(6章)を壊し、依存の更新が アロケーションを増やす。対策も機能と同じです——継続的に検査する。
- ベンチマークをCIに載せる — criterion(8章)は
基準値との比較(
--save-baseline)を持ち、 critcmpで差分を見られます - 時間ではなく命令数で測る選択肢 — 共有CIランナーの時間計測は ばらつきすぎます(上の表のとおり)。 iai-callgrindは 実行命令数・キャッシュミス数(8章の カウンタのシミュレーション版)で測るため、共有環境でも安定して 回帰を検出できます。「速さ」ではなく「仕事量」の定点観測です
- 閾値は緩く、傾向は厳しく — 例えば「1回の5%は雑音として流し、数回連続の3%は回帰として調べる」といった運用にします(数字は環境のばらつきに合わせて決めます)
- プロファイルの定点観測 — 月に一度フレームグラフ(8章)を 眺めるだけで、「いつの間にか太った」を早期発見できます
そして組織の規律として、性能の主張には数字を付ける。 「速くなったはず」のプルリクエストには、8章のループ (計測→変更→再計測)の出力を貼る。本書が全章でやってきた 「筆者の実測では」を、チームの習慣にすることです。
最後に、基礎編・応用編の28章を、5つの貫通原則に束ねます。 次の図が本書全体の地図です(主要な対応のみ示します)。
flowchart TB
p1["① 局所性<br/>データは近くに、まとめて"]
p2["② 並列度と独立性<br/>依存を切れば速くなる"]
p3["③ 待ちを隠し、境界を減らす<br/>重ねる・バッチ化する"]
p4["④ 表現がコストを決める<br/>数・レイアウト・構造の選択"]
p5["⑤ 計測が最終判断<br/>直感は外れる"]
p1 --- c1["2章 キャッシュ / 10章 コアレッシング<br/>12章 ループ順 / 14章 TLB / 15章 ブロッキング"]
p2 --- c2["3章 ILP / 4章 SIMD / 5章 マルチコア<br/>9章 SIMT / 17章 メモリモデル / 23章 粗粒度化"]
p3 --- c3["3章 パイプライン / 11章 非同期submit<br/>19章 async / 20章 FFI境界 / 24章 重ね合わせ / 27章 syscall"]
p4 --- c4["7章 ゼロコスト / 13章 数の表現<br/>18章 アリーナ / 22章 データ構造 / 25章 混合精度"]
p5 --- c5["1章 アセンブリ / 6章 コンパイラ / 8章 計測<br/>16章 top-down / 21章 ビルド / 26章 GPU計測 / 28章 分布"]
- ① 局所性 — キャッシュ、TLB、コアレッシング。速いメモリは 小さいから、使うものを近くに置く。本書で最も多くの倍率 (92倍、70倍、13倍…)を生んだ原則です
- ② 並列度と独立性 — ILPからSIMD、マルチコア、GPUまで、 現代の計算力はすべて並列です。並列を解放する鍵は常に 「依存を切る」ことでした
- ③ 待ちを隠し、境界を減らす — パイプライン、async、 GPUの重ね合わせ、syscallのバッチ化。待ちは消せなくても 重ねられる。境界は越える回数を減らせる
- ④ 表現がコストを決める — 同じ意味でも、数の形式・メモリの レイアウト・データ構造の選択で桁が変わる。抽象化は無料でも、 表現は無料ではない
- ⑤ 計測が最終判断 — そして、①〜④の適用が正しかったかは 計測だけが知っています。本書の実験でも、定石が覆った場面 (23章、21章のPGO)が何度もありました
この5つは、CPUにもGPUにもOSにも、そして今後現れる新しい ハードウェアにも適用できます。個別の数値(キャッシュのサイズ、 ワープの幅)は世代で変わりますが、原則と、それを検証する方法は 変わりません。
- レイテンシは分布で語ります。平均152nsの裏に33msの最大値が 隠れていました(実測)。p99系のパーセンタイルとSLOが実務の言葉です
- 負荷試験は協調的省略に注意します。「応答を待ってから次を送る」 計測は、最悪の期間を記録から落とします
- 「たまに遅い」の犯人一覧(リハッシュ、drop、ページフォールト、 ブロッキング混入、周波数、隣人)は本書の各章の索引でもあります
- 性能はテストと同じく回帰します。ベンチのCI化(criterion/ iai-callgrind)と、性能の主張に数字を添える文化が対策です
- 全28章は5つの原則——局所性、並列度、待ちと境界、表現、計測——に 束ねられます。個別の数値は世代で変わっても、原則は残ります
読み終えたあなたへ
Section titled “読み終えたあなたへ”本書のすべての実測は、リポジトリのsrc/snippets/とexamples/で
再現できます。あなたの環境で数字がどう変わるかを見ること——
それ自体が原則⑤の実践であり、この本の一番の宿題です。
さらに先へはさらに学ぶにはを、
言葉の確認には用語集をどうぞ。
よい計測を。そして、よい設計を。