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.
$ ./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
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ı
timestampValidBitsqueue 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/eBottomOfPipetü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:
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.
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_resetveya 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_queryAndroid 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.