首页 > 解决方案 > 将数据“预加载”到计算着色器的共享存储以加快读取访问是否有意义?

问题描述

我有以下计算着色器:

#version 450

layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in;

layout(push_constant) uniform PushConstant
{
    vec2 topLeft;
    vec2 bottomRight;
};

struct Position {
  float x, y, z;
};

layout (set=0, binding=0) buffer PositionBuffer
{
    Position positions[];
};

layout (set=0, binding=1) buffer SelectionBuffer
{
    uint selected[];
};

void main()
{
    uint ind = gl_GlobalInvocationID.z * (gl_WorkGroupSize.x * gl_NumWorkGroups.x) * (gl_WorkGroupSize.y * gl_NumWorkGroups.y)
               + gl_GlobalInvocationID.y * (gl_WorkGroupSize.x * gl_NumWorkGroups.x)
               + gl_GlobalInvocationID.x;

    Position pos = positions[ind];

    selected[ind] = 0;

    if(pos.x > topLeft.x && pos.x < bottomRight.x && pos.y > topLeft.y && pos.y < bottomRight.y)
    {
        selected[ind] = 1;
    }
}

它的作用是检查一个点(来自positions缓冲区)是否在用户提供的矩形内(来自 a PushConstant)。如果是 - 着色器通过写入缓冲区1来标记该点。selected

这段代码工作正常。但由于我对计算没有经验,所以我正在寻找让它变得更好的方法。我知道整个组都可以访问共享变量。这个想法是创建一个共享位置数组并将其填充到一个线程中,假设线程号为 0。然后,理论上,其他线程不需要读取缓冲区内存,而是更快的共享内存。

值得吗?
如何正确同步?
我可以做类似的事情来将数据写入selected数组吗?

标签: glslvulkancompute-shader

解决方案


从您的整体运营的角度来看待它。按顺序,您:

  1. 读取单个连续的内存块。
  2. 对该内存的每个值执行一次操作。
  3. 将该操作的结果写入另一块内存。

您的代码在任何时候都不需要多次读取该值。虽然编写的代码确实可能会写入两次值,但没有理由必须这样做。您可以根据条件轻松计算一个值,然后将该值写入内存。我会假设一个好的编译器会将你的代码准确地翻译成那个。

因为没有线程同时读取或写入多个位置,所以对内存的缓存访问仅有助于将“读取 X 字节”转换为更有效的“读取缓存行字节”读取。尝试从恰好位于同一缓存行中的地址读取的两个调用应该只执行一次内存提取。写作也是如此。写入同一缓存行的多个调用应聚合为单个写入。

当然,这需要合理的硬件。

假设这样的系统仍然可以调用同一内存的多个读/写。这与扭曲/波前中的调用次数有关(即:以锁步执行的着色器的调用次数)。如果每个 warp 读取的数据大小不是缓存对齐的,则两个 warp 可能会向同一缓存行发出读取,因为不同的 warp 可能同时执行。写入也是如此。但即使这样也假设缓存和执行内存获取的决定是基于每个 warp 进行的。

无论如何,如果确定是这种情况,那么正确的解决方案是尽可能地对齐您的阅读,而不是尝试为它完成缓存的工作。

有时预缓存数据会很有用,但这主要是在调用经常从相同地址读取的情况下,并且通常是在它们从彼此的内存中读取时。即使那样,这也是你应该描述的东西,而不是试图先验地编码。


推荐阅读