VK_KHR_shader_clock と clockRealtimeEXT でシェーダー内部をプロファイリングする方法

タイムスタンプクエリはコマンドバッファ側からパスを挟み込む。 シェーダー内部のどの分岐が時間を消費したかは分からない。 VK_KHR_shader_clock は読み取り可能なクロックをシェーダーの 内部に置く。各呼び出しが「今のサイクル数はいくつか」を 問い合わせ、サンプルの差分を取れる。VK_EXT_shader_realtime_clock と組み合わせれば、安定した GPU サイクルカウンタとナノ秒の ウォールクロックの両方が得られる。

2つのクロック

clocks.frag
#extension GL_EXT_shader_realtime_clock : require
#extension GL_ARB_shader_clock          : require  // デバイスサイクル

uint64_t c0 = clockARB();        // デバイス依存の単位(GPU サイクル)
// … 計測したいコード …
uint64_t c1 = clockARB();
uint64_t cycles = c1 - c0;       // 呼び出しごとのサイクル差分

// ウォールクロック版(GL_EXT_shader_realtime_clock):
uint64_t t0 = clockRealtimeEXT();   // あるエポックからの ns
uint64_t t1 = clockRealtimeEXT();

clockARB はサイクルだ。値は GPU クロックのスロットリングに応じて 変化するが、呼び出しごとには単調増加する。clockRealtimeEXT は ナノ秒だ。ホストの clock_gettime と同じエポックなので、GPU↔CPU の 相関が取れる。

設定方法

enable.cpp
// VK_KHR_shader_clock — デバイス/サブグループのサイクルカウンタ:
VkPhysicalDeviceShaderClockFeaturesKHR sc{};
sc.shaderDeviceClock   = VK_TRUE; // clockARB — デバイスサイクル
sc.shaderSubgroupClock = VK_TRUE; // *warp 同期*されたクロック —
                                  // 全レーンが同じ値を読む

// VK_EXT_shader_realtime_clock — ナノ秒のウォールクロック:
VkPhysicalDeviceShaderRealtimeClockFeaturesEXT rc{};
rc.shaderRealtimeClock = VK_TRUE; // clockRealtimeEXT — ns
// … 両方ともデバイスの pNext にチェインする …

パターン

profile.frag
// フラグメントごとのサイクル数をバッファに書き込む:
layout(set=0,binding=0) buffer Timings { uint64_t t[]; } times;

uint64_t start = clockARB();
vec4 c = expensiveLighting(...);   // ← 容疑者
times[gl_FragCoord.y * W + gl_FragCoord.x] = clockARB() - start;
// 読み戻し + ヒストグラム → どのピクセル/分岐が最もコストが高いか

できないこと

  • clockARB は呼び出しごとだ。warp 分岐があると最も遅いレーンの 数値が warp の数値になる。warp レベルの計測には shaderSubgroupClock と clock2x32ARB/shared 書き戻しを使う。
  • 呼び出し間のストールは計れない。スケジューラに隠される メモリレイテンシは単一呼び出しの差分には現れない。呼び出しの 集まりとして観測する必要がある。
  • clockRealtimeEXT のエポックは実装依存だ。差分を比較し、 デバイス間で絶対値を比較してはいけない。

実践的なルール

  • VK_KHR_shader_clock → shaderDeviceClock/shaderSubgroupClock; VK_EXT_shader_realtime_clock → shaderRealtimeClock。2つの 独立した拡張で、全体像には両方が必要だ。
  • warp レベルの計測には shaderSubgroupClock を使う。サブグループ内の 全レーンが同じカウンタを読むので、差分は1レーンではなく warp の ものになる。
  • clockARB はサイクルを返す。GPU 間で絶対サイクル数ではなく、2回の 実行の比率を比較する(ブーストクロックが異なるため)。
  • VK_EXT_shader_demote_to_helper_invocation と debugPrintfEXT (以前の記事)と組み合わせる。クロックは時間がどこへ行ったかを 教え、printf はどの分岐がそれを取ったかを教える。
  • パスレベルの計測には依然として VK_KHR_performance_query カウンタが 欲しい(別記事)。シェーダークロックはシェーダー内部の プロファイリング用、カウンタはハードウェアユニット用だ。

同じカテゴリの類似記事をチェック: Vulkan C++