线程分为三个级别,它们的调度方式不同。Warps 利用 SIMD 来获得更高的计算密度。线程块利用多线程来实现延迟容限。网格为跨 SM 的负载平衡提供独立的、粗粒度的工作单元。
经线中的线程
硬件一起执行 warp 的 32 个线程。它可以执行具有不同数据的单个指令的 32 个实例。如果线程采用不同的控制流,因此它们不会都执行相同的指令,那么这 32 个执行资源中的一些将在指令执行时处于空闲状态。这在 CUDA 参考中称为控制发散。
如果内核表现出很大的控制分歧,那么在这个级别上重新分配工作可能是值得的。这通过使所有执行资源在一个扭曲中保持忙碌来平衡工作。您可以在线程之间重新分配工作,如下所示。
// Identify which data should be processed
if (should_do_work(threadIdx.x)) {
int tmp_index = atomicAdd(&tmp_counter, 1);
tmp[tmp_index] = threadIdx.x;
}
__syncthreads();
// Assign that work to the first threads in the block
if (threadIdx.x < tmp_counter) {
int thread_index = tmp[threadIdx.x];
do_work(thread_index); // Thread threadIdx.x does work on behalf of thread tmp[threadIdx.x]
}
块中的翘曲
在 SM 上,硬件调度会扭曲到执行单元上。一些指令需要一段时间才能完成,因此调度程序交错执行多个 warp 以保持执行单元忙碌。如果某些 warp 尚未准备好执行,则会跳过它们而不会降低性能。
在这个级别通常不需要负载平衡。只需确保每个线程块有足够的 warp 可用,以便调度程序始终可以找到准备执行的 warp。
网格中的块
运行时系统将块调度到 SM 上。多个块可以在一个 SM 上同时运行。
在这个级别通常不需要负载平衡。只需确保有足够的线程块可用于多次填充所有 SM。当一些 SM 空闲并且没有更多线程块准备好执行时,过度供应线程块以最小化内核结束时的负载不平衡是有用的。