在 Unity Compute Shader 中调用 numthreads 和 Dispatch 的区别

Difference Between Calling numthreads and Dispatch in a Unity Compute Shader

假设我想使用计算着色器 运行 Kernel_X 使用 (8, 1, 1) 的线程尺寸。

我可以将其设置为:

在脚本中:

Shader.Dispatch(Kernel_X, 8, 1, 1);

在着色器中:

[numthreads(1,1,1)]
void Kernel_X(uint id : SV_DispatchThreadID) { ... }

或者我可以这样设置:

在脚本中:

Shader.Dispatch(Kernel_X, 1, 1, 1);

在着色器中:

[numthreads(8,1,1)]
void Kernel_X(uint id : SV_DispatchThreadID) { ... }

我知道在这段代码的最后,尺寸会变成 (8, 1, 1);但是,我想知道切换数字实际上是如何相互不同的。我的猜测是 运行ning Dispatch (Kernel_X, 8, 1, 1), "运行" 一个 1x1x1 的内核 8 次,而 运行ning numthreads(8 ,1,1) 将 运行 一次 8x1x1 内核。

Dispatch() 调用决定了您正在调用的线程组的数量。这样你就调用了 8 次 1 次 1 = 8 组。

Shader.Dispatch(Kernel_X, 8, 1, 1);

并且在着色器中,[numthreads] 标记指定线程组的大小。例如,这为每个组声明了 8 次 1 次 1 = 8 个线程。

[numthreads(8,1,1)] void Kernel_X(uint id : SV_DispatchThreadID)
{ }

如果要实现总共8个线程,可以调用一个组,每个组8个线程,或者8个组,每个组一个线程。最终结果将是相同的,尽管性能不同。通常,您可能希望线程组大小为 2 的幂,对于 nvidia,您通常将其至少设置为 32,而 AMD 卡针对每组至少 64 个线程进行了优化。

顺便说一句,您通常会分派超过 8 个线程,因为为仅 8 个线程编写计算着色器是毫无意义的,而您的 cpu 可能会更快。因此,您可能想致电:

Shader.Dispatch(Kernel_X, Mathf.CeilToInt((float)wantedThreadNumber/wantedGroupSize), 1, 1);

要了解差异,需要一些硬件知识:

在内部,GPU 在所谓的波前工作,这是 SIMD 风格的处理单元(就像一组线程,每个线程都可以拥有自己的数据,但它们 都必须 始终在完全相同的时间执行完全相同的指令)。每个波前的线程数取决于硬件,但通常为 32 (NVidia) 或 64 (AMD)。

现在,对于 [numthreads(8,1,1)],您请求大小为 8 x 1 x 1 = 8 个线程的着色器线程组,硬件可在其波阵面之间自由分配。因此,每个波前有 32 个线程,硬件会为每个着色器组安排一个波前,该波前有 8 个活动线程(其他 24 个线程是“非活动”的,这意味着它们做同样的工作,但正在丢弃任何内存写)。然后,使用 Dispatch(1, 1, 1),您将调度一个这样的着色器组,这意味着硬件上将有一个波前 运行ning。

你会用 [numthreads(1,1,1)] 代替吗,波阵面中只有一个线程可以处于活动状态。因此,通过在那个上调用 Dispatch(8, 1, 1),硬件将需要 运行 8 个着色器组(= 8 个波阵面),每个 运行ning 仅具有 1/32 个活动线程,因此虽然你会得到相同的结果,但你会浪费更多的计算能力。

因此,一般来说,为了获得最佳性能,您希望着色器组大小为 32(或 64)的倍数,同时尝试使用尽可能少的数字调用 Dispatch。