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

问题描述 投票:2回答:1

我有以下计算着色器:

#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数组吗?

glsl vulkan compute-shader
1个回答
3
投票

从整个操作的角度来看它。按顺序,你:

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

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

由于没有线程一次读取或写入多个位置,因此对存储器的高速缓存访​​问仅有助于将“读取X字节”转换为更有效的“读取高速缓存行字节”读取。尝试从碰巧生活在同一缓存行中的地址读取的两个调用应该只执行一次内存提取。写作也是如此;写入同一缓存行的多个调用应聚合为单个写入。

当然,这假设是合理的硬件。

对于这样的系统来说,仍然可以调用同一存储器的多次读/写。这与warp / wavefront中的调用次数有关(即:以锁定步骤执行的着色器的调用次数)。如果每个warp读取的数据大小不是高速缓存对齐的,则两个warp可能会向同一高速缓存行发出读取,因为不同的warp可能同时执行。写作也是如此。但即便如此,也可以假定执行内存提取的缓存和决策是基于每个warp进行的。

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

有时预缓存数据会很有用,但这主要是在调用经常从同一地址读取的情况下,通常是在他们从彼此的内存中读取时。即便如此,这是你应该描述的东西,而不是试图以先验的方式编码。

© www.soinside.com 2019 - 2024. All rights reserved.