微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

我正在尝试使用 HLSL 进行 GPU 计算,但我面临性能下降问题。 我有一个包含 1,200,000 个值的数组,它模拟了一个二维数组 [1200,1000]。

所以...

我的任务是生成从每个值 [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)]
void Spreading (uint3 id: SV_dispatchThreadID)

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

我做错了什么,我能做些什么?

解决方法

编写高效的计算着色器很难,您需要了解硬件的工作原理,即便如此,由于多线程在 GPU 上的工作方式,编写对复杂算法高效且正确的计算着色器通常也不是一件容易的事.

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。