Measuring GPU Execution Time with WebGPU Timestamp Queries
Learn how to use WebGPU's timestamp‑query feature to measure GPU execution time accurately, with a step‑by‑step example, trade‑offs, and production tips.
27 Aug 2026, 05:07 UTC

The Need for Accurate GPU Timing
When a shader or compute pass is the bottleneck, developers often rely on performance.now() to decide where to optimise. That function measures CPU time between JavaScript calls, not how long the GPU spends executing commands. Because the GPU queues work asynchronously, the JavaScript thread can finish while the GPU is still busy. This mismatch leads to misleading optimisation decisions.
Why WebGPU's Timestamp Query Feature Matters
WebGPU exposes a timestamp‑query feature that lets you record the hardware clock at arbitrary points in a command stream. By bracketing a workload with two timestamps you get a hardware‑level duration in nanoseconds, independent of JavaScript scheduling or CPU overhead. This is the only way to measure GPU work precisely in a browser environment.
1️⃣ Enabling the Feature
Timestamp queries are optional. Before you request a device, check that the adapter advertises the feature:
const adapter = await navigator.gpu.requestAdapter();
const features = adapter.features;
if (!features.has('timestamp-query')) {
console.warn('Timestamp queries not supported on this device.');
// Fallback to CPU timing or skip profiling.
}
const device = await adapter.requestDevice({
requiredFeatures: features.has('timestamp-query') ? ['timestamp-query'] : []
});
Creating a query set on an unsupported device throws; the above guard prevents that.
2️⃣ Setting Up a Query Set and Buffer
A query set is a GPU‑side array that stores timestamp ticks. For a single measurement you need two slots (start and end). A separate buffer is required to copy those ticks into CPU‑accessible memory.
const querySet = device.createQuerySet({
type: 'timestamp',
count: 2
});
const timestampBuffer = device.createBuffer({
size: 16, // 2 × 64‑bit values
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
3️⃣ Recording Timestamps in a Render Pass
Insert writeTimestamp calls before and after the workload. They must be inside a command encoder. The following example brackets a simple full‑screen triangle draw:
const encoder = device.createCommandEncoder();
// Record start time
encoder.writeTimestamp(querySet, 0);
// Begin render pass
const pass = encoder.beginRenderPass({
colorAttachments: [{
view: renderTargetView,
loadOp: 'clear',
storeOp: 'store',
clearValue: { r: 0, g: 0, b: 0, a: 1 }
}]
});
pass.draw(); // Actual GPU work
pass.end();
// Record end time
encoder.writeTimestamp(querySet, 1);
// Resolve the timestamps into the buffer
encoder.resolveQuerySet(querySet, 0, timestampBuffer, 2);
// Submit the command bundle
device.queue.submit([encoder.finish()]);
4️⃣ Reading and Converting the Result
After the queue finishes, map the buffer for reading. The GPU returns ticks; use device.getTimestampPeriod() to convert ticks to nanoseconds.
await timestampBuffer.mapAsync(GPUMapMode.READ);
const data = new BigUint64Array(timestampBuffer.getMappedRange());
const periodNs = device.getTimestampPeriod(); // In nanoseconds per tick
const durationNs = Number(data[1] - data[0]) * periodNs;
console.log(`GPU execution time: ${durationNs / 1_000_000} ms`);
timestampBuffer.unmap();
Verify that durationNs is non‑zero and scales with workload complexity. If you see zero or an unexpected value, the device may not support the feature or the query may have been resolved too early.
Trade‑offs and Practical Limits
- Optional Support: Many GPUs or browsers disable
timestamp-queryfor security reasons. Always guard against missing support. - Pipeline Synchronisation: Resolving a query forces a pipeline stall until the GPU writes the value. Excessive queries per frame can lower frame rates.
- Zero or Overflow: Some implementations return zero when queries are not honoured. Check that
data[1] > data[0]before converting. - Readback Latency: Mapping the buffer is asynchronous; reading too early yields undefined data. Always await
mapAsyncand the queue submit promise.
Actionable Checklist for Production Profiling
- Detect
timestamp-querysupport during device creation. - Batch multiple measurements into a single query set where possible to minimise stalls.
- Validate the period conversion by comparing a known GPU‑heavy workload against
performance.now()as a sanity check. - Fallback to CPU timing when the feature is unavailable, but note that it only measures submission time.
- Log results to a lightweight profiling UI and keep the buffer small (<64 KB) to avoid memory pressure.
Conclusion
WebGPU's timestamp queries give developers a low‑overhead window into the GPU's clock, enabling precise profiling of shaders and compute workloads. By following the enable‑check, write‑resolve‑read pattern and respecting the feature's limits, you can integrate robust GPU timing into your rendering pipeline and make informed optimisation decisions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.