Timestamp query'leriyle Vulkan GPU süresi profil çıkarma

Post 56. vkQueueSubmit çevresinde wall-clock ölçümü hiçbir şey söylemez — GPU işi asenkrondur. Çekirdek Vulkan’ın cevabı: timestamp query’leri. vkCmdWriteTimestamp GPU’ya kendi saatini bracket’ladığınız pipeline stage’inde query pool’a yazdırır, vkGetQueryPoolResults geri okur. Delta × timestampPeriod = gerçek GPU süresi.

example.sh
$ ./app --headless out.ppm
queue family 0: timestampValidBits=64
timestampPeriod: 10.0 ns
draw: 525.4 us GPU  (802.9 us submit->done incl. CPU overhead)

İki sayı arasındaki fark tam olarak mesele — CPU süresi submit overhead’i, driver işi ve gecikmeyi içerir; query yalnızca GPU’yu ölçer.

Desen

main.cpp (query pool)
auto qp = device->createQueryPoolUnique(
    {{}, vk::QueryType::eTimestamp, 2});       // ölçüm başına 2 slot

// command buffer'da:
c->resetQueryPool(qp.get(), 0, 2);
c->writeTimestamp(eTopOfPipe,    qp.get(), 0); // işten önce
//   ... render / dispatch ...
c->writeTimestamp(eBottomOfPipe, qp.get(), 1); // sonra
c->end(); queue.submit(si); queue.waitIdle();

uint64_t ts[2];
device->getQueryPoolResults(qp.get(), 0, 2, sizeof(ts), ts, 8,
    e64 | eWait);
float us = (ts[1] - ts[0]) * props.limits.timestampPeriod / 1000.f;

Alanların anlamı

  • timestampValidBits queue family başına (vkGetPhysicalDeviceQueueFamilyProperties): 0 ise o family timestamp yapamaz — masaüstü GPU’larda graphics family’ler 64 bildirir. Varsaymayın, kontrol edin.
  • timestampPeriod (VkPhysicalDeviceLimits): tick başına nanosaniye — bu RADV’de 10ns, başka yerlerde çok değişir. Her zaman çarpın, ham tick’leri cihazlar arası asla karşılaştırmayın.
  • Stage flag’leri: eTopOfPipe/eBottomOfPipe tüm aralığı bracket’lar; daha ince stage’ler (eVertexShader, eFragmentShader) zamanın nereye gittiğini lokalize eder — ama stage’ler sadece sıralar, GPU’yu o noktada bekletmezler.

Üretim deseni: blocking beklemeler değil, ring

eWait CPU’yu durdurur — demo için tamam ama frame başına yanlış. Gerçek desen bir query ring‘dir: frame-in-flight başına 2×N slot ayırın, fence’inden sonra i-N slotunu okuyun — sıfır stall:

per-frame
constexpr int kDepth = 3;              // frame in flight
auto qp = dev->createQueryPoolUnique(
    {{}, eTimestamp, 2 * kDepth});
// frame f, 2*(f%kDepth) slotlarına yazar; fence'i az önce sinyal
// veren frame'in slotlarını oku — sonuçlar hazır, bekleme gerekmez
getQueryPoolResults(qp, f2*2, 2, ..., e64 /* eWait yok */);

GPU↔CPU hizalama: calibrated timestamp’ler

“Kullanıcı input ile piksel arasında ne kadar bekledi” için iki saati tek eksene koymanız gerekir. VK_KHR_calibrated_timestamps (bu driver’da mevcut) CLOCK_MONOTONIC/_RAW ve GPU saatini tek çağrıda örnekler — bir kez çözün, sonra her GPU tick’i frame-latency hesabı için wall time’a eşlenir.

(calibration)
vk::CalibratedTimestampInfoKHR infos[2] = {
    {vk::TimeDomainKHR::eDevice},
    {vk::TimeDomainKHR::eMonotonic}};
uint64_t ts[2], maxDev;
dev->getCalibratedTimestampsKHR(2, infos, ts, &maxDev, dldi);
// ts[0]/ts[1] birbirinin maxDev ns yakınında alınır

Tuzaklar

  • Önce ısın: ilk kullanım pipeline’ları driver’da JIT/optimize olur — warmup draw sonrası ölçün yoksa shader derlemeyi ölçersiniz.
  • Pool dışı okumalar çöp döndürür — yazmadan önce her zaman resetQueryPool (VK_EXT_host_query_reset veya cmd-içi reset gerekir).
  • Timestamp’ler serialize etmez: iki yazma birbirine sadece sıra verir; fence değildirler — çevre işleri overlap olabilir.
  • Vendor counter’ları başka yerde: VK_KHR_performance_query Android tiled-GPU bölgesi; masaüstünde daha derin counter’lar için RenderDoc/Tracy/NSight Graphics kullanın — timestamp query’ler taşınabilir uygulama-içi minimumdur.

Tam program

Example: VulkanExamples/p56_timestamp

— main.cpp: kasıtlı pahalı bir fragment shader (400 sin/cos iterasyonu), timestamp’li draw, GPU µs vs CPU wall time yazdırır; check.py ölçümün makul olduğunu assert eder.


Check out similar posts by category: C++ Vulkan