如何使用 Vulkan 时间戳查询?
How to use Vulkan Timestamp Queries?
这是我尝试测量 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 个命令进行了预处理,但事实证明这不是真的。
- 放置第二个时间戳我希望查询值将在所有先前的命令完成后写入。 IE。这两个查询值之间的时间差应该给我 GPU 完成单个帧的所有工作所需的时间。
- 我正在考虑
VkPhysicalDeviceLimits::timestampPeriod
(在我的机器上为 1)和 VkQueueFamilyProperties::timestampValidBits
(在我的机器上为 64)
我创建了一个大数据集,视觉上渲染一帧大约需要 2 秒(~2000 毫秒)。但是计算出的时间只有 2(两个)不同的值 - 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);
现在,当预处理器命中时间戳命令时,它会立即写入查询值,因为没有先前的工作,也没有什么可等待的(记住空闲设备)。这次我有另一个更接近真值的值:
20.9336ms
20.9736ms
21.036ms
21.0196ms
20.9572ms
21.3586ms
...
哪个更好但仍然远远超出预期~2000ms。
这是怎么回事,当我设置时间戳时设备内部发生了什么,如何获得正确的值?
虽然 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
查询整组工作从开始到结束的时间。
所以将第一个查询放在您要测量其时间的工作之前。
这是我尝试测量 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 个命令进行了预处理,但事实证明这不是真的。
- 放置第二个时间戳我希望查询值将在所有先前的命令完成后写入。 IE。这两个查询值之间的时间差应该给我 GPU 完成单个帧的所有工作所需的时间。
- 我正在考虑
VkPhysicalDeviceLimits::timestampPeriod
(在我的机器上为 1)和VkQueueFamilyProperties::timestampValidBits
(在我的机器上为 64)
我创建了一个大数据集,视觉上渲染一帧大约需要 2 秒(~2000 毫秒)。但是计算出的时间只有 2(两个)不同的值 - 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);
现在,当预处理器命中时间戳命令时,它会立即写入查询值,因为没有先前的工作,也没有什么可等待的(记住空闲设备)。这次我有另一个更接近真值的值:
20.9336ms
20.9736ms
21.036ms
21.0196ms
20.9572ms
21.3586ms
...
哪个更好但仍然远远超出预期~2000ms。
这是怎么回事,当我设置时间戳时设备内部发生了什么,如何获得正确的值?
虽然 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
查询整组工作从开始到结束的时间。
所以将第一个查询放在您要测量其时间的工作之前。