タイムスタンプクエリはコマンドバッファ側からパスを挟み込む。
シェーダー内部のどの分岐が時間を消費したかは分からない。
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カウンタが 欲しい(別記事)。シェーダークロックはシェーダー内部の プロファイリング用、カウンタはハードウェアユニット用だ。
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください