这是我试图测量GPU工作负载的简化伪代码:
for(N) vkCmdDrawIndexed();
vkCmdWriteTimestamp(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
vkCmdWriteTimestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
submit();
vkDeviceWaitIdle();
vkGetQueryPoolResults();
需要注意的事项:
- N在我的情况下是224
- 我必须等待一个空闲的设备——如果没有它,我会继续收到一个验证错误,说我的数据还没有准备好,尽管我有多个查询池在运行
- 放入第一个时间戳,我希望在所有先前的命令到达预处理步骤后立即写入查询值。我很确定所有224个命令几乎同时被预处理,但现实表明这不是真的
- 放入第二个时间戳,我预计查询值将在之前的所有命令完成后写入。也就是说,这两个查询值之间的时间差应该给我GPU为一帧完成所有工作所需的时间
- 我正在考虑
VkPhysicalDeviceLimits::timestampPeriod
(我的机器上为1(和VkQueueFamilyProperties::timestampValidBits
(我的计算机上为64(
我创建了一个大数据集,在视觉上渲染一帧大约需要2秒(约2000毫秒(。但计算出的时间只有两(两(个不同的值——0.001024ms或0.002048ms,因此逐帧输出可能如下所示:
0.001024ms
0.001024ms
0.002048ms
0.001024ms
0.002048ms
0.002048ms
...
不知道你怎么样,但我觉得这些价值观非常可疑。我对此没有答案。也许当时,最后一个绘制命令到达命令处理器,所有的工作都已经完成了,但为什么是1024和2048呢??
我试图修改代码并移动上面的第一个时间戳,即:
vkCmdWriteTimestamp(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
for(N) vkCmdDrawIndexed();
vkCmdWriteTimestamp(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
现在,当预处理器点击timestamp命令时,它会立即写入查询值,因为之前没有任何工作,也没有什么可等待的(记住空闲设备(。这一次我有了另一个更接近真相的价值观:
20.9336ms
20.9736ms
21.036ms
21.0196ms
20.9572ms
21.3586ms
...
这是更好的,但仍远远超出预期约2000米。
当我设置时间戳时,设备内部发生了什么,如何获得正确的值?
虽然Vulkan中的命令可能会被无序执行(在某些限制范围内(,但您不应该广泛地期望命令被无序执行。计时器查询尤其如此,如果它们被无序执行,就其含义而言,将是不可靠的。
考虑到这一点,您的代码是这样说的:;做一堆工作。然后查询管道开始准备执行新命令所需的时间,然后查询到达管道结束所需时间"好吧,管道的起点可能只有在大部分工作完成后才能执行新命令。
基本上,你认为正在发生的是:
top work work work work work work | timer
stage1 work work work work work work
stage2 work work work work work work
bottom work work work work work work | timer
但是没有任何东西需要GPU以这种方式执行。几乎可以肯定的是,实际发生的是:
time->
top work work work work work work | timer
stage1 work work work work work work
stage2 work work work work work work
bottom work work work work work work | timer
所以你的两个计时器只得到了实际工作的一小部分。
您想要的是:
top timer | work work work work work work
stage1 work work work work work work
stage2 work work work work work work
bottom work work work work work work | timer
这将查询整个工作集从开始到结束的时间。
因此,将第一个查询放在要测量其时间的工作之前。