将数据“预加载”到计算着色器的共享存储中以实现更快的读取访问是否有意义?

时间:2019-02-13 15:30:54

标签: glsl vulkan compute-shader

我有以下计算着色器:

#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缓冲区的某个点是否位于用户提供的矩形(来自PushConstant的矩形内)。如果是,则着色器通过将1写入selected缓冲区来标记该点。

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

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

1 个答案:

答案 0 :(得分:3)

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

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

您的代码绝对不需要多次读取该值。尽管编写的代码可能会写入两次值,但没有理由必须。您可以轻松地根据条件计算一个值,然后将该值写入内存。而且我会假设一个好的编译器会将您的代码精确地翻译成这样。

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

当然,这假设硬件合理。

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

无论如何,如果确定是这种情况,正确的解决方案是尽可能地使您的读数对齐,而不是尝试为此做缓存的工作。

有时预缓存数据会很有用,但这主要是在调用频繁从同一地址读取且通常是从彼此的内存读取数据的情况下进行。即使这样,您也应该对此进行概述,而不是尝试先验地进行编码。