GPUの計測
この章でわかること:
- GPU自身の時計で計測するタイムスタンプクエリの実装(wgpu)
- 「カーネル4マイクロ秒、往復190マイクロ秒」——壁時計との乖離の実測
- GPU計測に固有の罠(周波数の変動、非同期性)
- GPUプロファイラという上位の道具
- なぜ体系に必須か — 8章で「計測なき最適化は 推測」と学びました。GPUの計測は壁時計では原理的に不正確で (12章の注記)、専用の方法を知らなければ Part VIの最適化は回りません
この章の実験はリポジトリのexamples/ch26-timestampです。
cd examplescargo run --release -p ch26-timestampなぜ壁時計ではだめか
Section titled “なぜ壁時計ではだめか”これまでのGPU計測は「submitしてから完了を待ち終えるまで」の
壁時計でした。この値には、コマンドの転送、キューの待ち、
完了通知、CPU側の同期(24章)がすべて
混ざっています。大きなカーネルなら誤差ですが、
小さなカーネルでは測っているものの大半がカーネル以外になります。
解決策は、GPU自身に時刻を記録させることです。
タイムスタンプクエリの実装
Section titled “タイムスタンプクエリの実装”WebGPUのタイムスタンプクエリ(timestamp query)は、 コマンド列の特定の位置でGPUの内部時計を記録する仕組みです。 wgpuでの手順は4つです(完全なコードはリポジトリ参照)。
// 1. 機能を要求する(対応確認つき)// Features::TIMESTAMP_QUERY
// 2. 記録先のQuerySetを作る(タイムスタンプ2個ぶん)let query_set = device.create_query_set(&wgpu::QuerySetDescriptor { ty: wgpu::QueryType::Timestamp, count: 2, label: None,});
// 3. コンピュートパスの開始と終了で記録するよう指定するlet mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { timestamp_writes: Some(wgpu::ComputePassTimestampWrites { query_set: &query_set, beginning_of_pass_write_index: Some(0), end_of_pass_write_index: Some(1), }), label: None,});
// 4. 結果(u64×2)をバッファへ解決→読み戻し、目盛りをns換算するencoder.resolve_query_set(&query_set, 0..2, &buf_resolve, 0);// 経過ns = (t1 - t0) × queue.get_timestamp_period()記録される値はGPU固有の「目盛り(tick)」なので、
queue.get_timestamp_period()(1目盛りが何ナノ秒か)を掛けて
時間に直します。
実測 — 乖離を数字にする
Section titled “実測 — 乖離を数字にする”同じカーネルを、大きな仕事(1677万要素)と極小の仕事(256要素)で 実行し、「GPU時計のカーネル時間」と「壁時計」を並べます。 筆者の実測(Apple M4)です。
16M要素 カーネル(GPU時計): 9.9〜11.3ms | 壁時計: 10.3ms256要素 カーネル(GPU時計): 0.0043ms | 壁時計: 0.19ms2行目が決定的です。カーネルの実行は4.3マイクロ秒なのに、 壁時計は190マイクロ秒——45倍です。 11章で「ベクトル加算はAPIの往復に飲み込まれる」と 述べたことが、そのまま数字になりました。壁時計だけを見て 「このカーネルは0.2ミリ秒かかる」と結論して最適化を始めたら、 存在しない45倍分の遅さを追いかけることになります。 一方、大きな仕事ではカーネル≒壁時計で、往復のコストは誤差に沈みます。
測る対象に合わせて計器を選ぶ——スループットを知りたいなら タイムスタンプクエリ、エンドツーエンドのレイテンシを知りたいなら 壁時計。8章の「何を測っているかを知る」の、GPU版です。
GPU計測の罠
Section titled “GPU計測の罠”実験の生データには、もう1つ教材が含まれていました。 16M要素の計測1回目だけ、カーネル時間(26.6ms)が壁時計(13.5ms)を 上回るという物理的にありえない値が出たのです(2回目以降は正常)。
原因は特定できていません。有力な容疑者は、GPUの周波数の変動
(GPUもCPUと同じく負荷に応じてクロックを上下させます——
8章で見た計測の注意のGPU版)と、目盛りを
ns換算する係数(get_timestamp_period)がバックエンドの近似値で
ある可能性です。いずれにせよ実務の対策は同じです——
ウォームアップして、複数回測り、異常値を疑って捨てる。8章の規律はGPUでも
一言一句そのまま有効です。
もう1つの罠は非同期性です。タイムスタンプは「パスの開始と終了」を 記録しますが、GPUは複数のパスを重ねて実行することがあります。 隣のパスと重なった時間も含まれるため、正確に測りたいカーネルは 単独のsubmitで測るのが安全です(24章の 重ね合わせは、性能には善、計測には毒です)。
プロファイラ — その先の道具
Section titled “プロファイラ — その先の道具”タイムスタンプクエリは8章のInstantに
相当する基本計器です。その先には、CPUのプロファイラに相当する
専用ツールがあります。
- Xcode GPUデバッガ / Metal System Trace(macOS) — パスごとの時間、占有率、帯域、シェーダのホットスポットまで GUIで掘れます。wgpuのMetalバックエンドの計測にも使えます
- NVIDIA Nsight Systems / Nsight Compute — CUDA/Vulkan圏の定番。 Nsight Computeはカーネル1個をルーフライン(10章) 上にプロットするところまでやってくれます
- wgpu-profilerクレート — この章のタイムスタンプ処理をスコープ単位に自動化した既製品です
16章のtop-down分析に相当する体系 (演算律速か、帯域律速か、占有率不足か)は、これらのプロファイラの メトリクスで組み立てます。本書で学んだ語彙——占有率、 コアレッシング、算術強度——が、そのままツールの画面の 読み方になります。
- GPUの正確な計測はGPU自身の時計(タイムスタンプクエリ)で行います。
wgpuではQuerySet+
timestamp_writes+resolve_query_setの定型です - 実測では、極小カーネルの壁時計はカーネル実行時間の45倍でした。 スループットはGPU時計、レイテンシは壁時計と、目的で計器を選びます
- GPUにも周波数変動があり、ウォームアップと複数回計測という 8章の規律がそのまま必要です(実測で異常値も観測しました)
- 本格的な分析はXcode GPUデバッガやNsightへ。本書の語彙が そのまま画面の読み方になります
Part VIはここまでです。最後のPart VIIでは、プログラムの外側—— OSの層のコストと、実務で性能を運用する方法論——で体系を閉じます。