如何在hlsl上正确有效地配置计算机着色器中线程的同步



我正试图使用HLSL进行GPU计算,但我面临性能下降的问题。我有一个值为1200000的数组​​其模拟2D阵列[12001000]。

所以。。。

我的任务是生成从每个值[X,Y]到[X+shift_X,Y+1]的分布,乘以系数数组中的系数。这必须严格按照Y轴的顺序进行。如果这是以伪代码的形式编写的,那么它将是这样的:

float[] data = new data[x_count * y_count];
...
for (int y = 0, y < y_count - 1, y++)
{
for (int x = 0, x < x_count, x++)
{
int spread_shift = (spread.Length - 1) / 2u;
for (int s = 0; s < spread.Length; s++)
{
int shift_ind = x + s - spread_shift;
data[x + (y + 1) * x_count] += data[shift_ind + id.y * x_count] * spread[s];
}
}
}

为了避免比赛,我为小组增加了一道障碍。它起作用,但非常缓慢。

现在我的c#代码看起来是这样的:

int x_count = 1200;
int y_count = 1000;
shader.Dispatch (spreading_index, x_count, y_count - 1, 1);

在我的着色器中是这样的:

[numthreads(1, 1, 1)]
void Spreading (uint3 id : SV_DispatchThreadID)
{
int shift_ind;
int spread_len = spread.Length;
int spread_shift = (spread_len - 1) / 2u;
if (id.x == 0 | id.x == (uint)(x_count - 1)) { data[id.x + id.y * x_count] = border_coef; }
else { data[id.x + id.y * x_count] += border_coef_arr[id.x]; }

for (int s = 0; s < spread_len; s++)
{
shift_ind = id.x + s - spread_shift;
if (shift_ind > (x_count - 1)) continue;
else if (shift_ind < 0) continue;
data[id.x + (id.y + 1) * x_count] += data[shift_ind + id.y * x_count] * spread[s];
}

GroupMemoryBarrierWithGroupSync();
}

如果我调用这样的方法:

shader.Dispatch (spreading_index, x_count / 25, z_count - 1, 1);

[numthreads (25, 1, 1)]
void Spreading (uint3 id: SV_DispatchThreadID)

它开始工作得更快,但结果并不正确。

我做错了什么?我该怎么办?

编写高效的计算着色器很难,您需要了解硬件是如何工作的,即使这样,由于GPU上的多线程工作方式,编写一个高效且适用于复杂算法的计算着色器通常也不是一件容易的事。

  1. 您的GPU将在波形字体之间分配工作,波形字体是必不可少的线程阵列-通常每个波形字体32或64个线程(32是最常见的。只有一些AMD GPU有64个(。每个波形字体一次只能执行一个线程组。通过指定[numthreads(1, 1, 1)],可以说一个线程组只包含一个线程(1 x 1 x 1(,因此在wave字体中的所有32/64个线程中,只有一个线程将执行实际工作,而其余线程处于空闲状态(因为它们在此期间无法执行不同的组(。这就是为什么你的计算着色器如此缓慢:你最多只使用1/32的GPU。理想情况下,您希望您的组大小是波浪字体大小的倍数,因此像[numthreads(32, 1, 1)][numthreads(64, 1, 1)]这样的东西将达到最佳性能(取决于您的GPU(
  2. GroupMemoryBarrierWithGroupSync()仅在组中的线程之间同步。因此,如果你的组只包含一个线程,这将毫无作用,如果它包含多个线程,它显然不会做你认为它会做的事情
  3. GroupMemoryBarrierWithGroupSync()实际做的是,它保证该组内该行之前的所有工作将在该行之后的任何工作执行之前执行。因此,它永远不会将您的120万个线程全部同步,只同步其中的一小部分,而且它只在该行的"before"one_answers"after"之间分配工作
  4. 如果我正确理解您的代码,那么数据数组中的每个条目都包含前一行中多个条目的加权和。在单个计算着色器调度中执行这样的操作是极其困难的(尽管如果您非常巧妙地使用一些信息和技巧,可能并非不可能(,因为每个线程都需要读取其他线程当前可能正在写入的多个值,并且该访问模式没有被严格包含。作为一个更简单的解决方案,我建议您的计算着色器一次只处理一行(每个线程只计算数组中的一个条目,并且所有线程都具有相同的y值作为输入(,然后只执行多个shader.Dispatch(...)调用(每行一个(。通过这种方式,您可以使用UAV屏障将一个调度调用与下一个调用同步(取决于您使用的API,这些屏障可能会自动插入,或者您必须手动插入(,并且计算着色器的任何线程都无法读取另一个线程当前正在写入的值

最新更新