在 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。
假设我想使用计算着色器 运行 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。