コンテンツにスキップ

実践の性能工学

この章でわかること:

  • レイテンシを「分布」で語る方法 — 平均と中央値と最悪値は別の生き物 (実測: 中央値70ナノ秒、最大33ミリ秒)
  • 計測を歪める協調的省略(coordinated omission)という罠
  • 性能の回帰を防ぐ運用 — ベンチマークのCI化と安定化
  • 本書全体の知識の地図 — 28章を貫く5つの原則
  • なぜ体系に必須か — 知識は運用されて初めて性能になります。 個人の技能を、チームと時間に耐える仕組みへ変換するのが最終章です

Webアプリケーションの性能は、ほとんどの場合レイテンシ (2章で導入した言葉の、 サービス応答時間への適用)で語られます。そして最初に 捨てるべき習慣が「平均で語る」ことです。

実験します。HashMapへの100万件の挿入を、1回ずつ個別に 計測して分布を見ます。

100万回のinsertの、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]);
}
stable / releasePlaygroundで開く ↗

筆者の実測(Playground)です。

平均 : 152 ns
中央値 : 70 ns
p99 : 260 ns
p99.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つの原則——局所性、並列度、待ちと境界、表現、計測——に 束ねられます。個別の数値は世代で変わっても、原則は残ります

本書のすべての実測は、リポジトリのsrc/snippets/examples/で 再現できます。あなたの環境で数字がどう変わるかを見ること—— それ自体が原則⑤の実践であり、この本の一番の宿題です。 さらに先へはさらに学ぶにはを、 言葉の確認には用語集をどうぞ。

よい計測を。そして、よい設計を。