线程和执行
并行的关键是使用多个线程来解决问题(duh。)但是在线程的组织方式上,经典的多线程编程存在一些差异。
首先让我们谈谈你的典型 GPU,为了简单起见,我将重点关注
GPU 具有许多处理核心,这使其成为并行执行多个线程的理想选择。这些核心在流处理器(SM,NVidia 术语)中组织,其中 GPU 具有给定数量。
在 SM 内部运行的所有线程都称为线程块。SM 上可以有比线程更多的线程。核心数量定义了所谓的扭曲大小(NVidia 术语)。线程块内的线程被称为 warps
。
一个跟进的快速示例:典型的 NVidia SM 有 32 个处理核心,因此它的 warp 大小为 32.如果我的线程块现在有 128 个线程要运行,它们将被束缚为 4 个经线(4 个经线* 32 个经线尺寸= 128 个)线程)。
稍后选择线程数时,warp 大小非常重要。
单个 warp 中的所有线程共享一个指令计数器。这意味着这 32 个线程真正同步,因为每个线程同时执行每个命令。这是一个性能缺陷:这也适用于内核中的分支语句!
示例:我有一个内核,它有一个 if 语句和两个分支。在 warp 中我的 16 个线程将执行分支一,另一个 16 分支两个。直到 if 语句,warp 中的所有线程都是同步的。现在有一半人选择了不同的分支。发生的事情是,另一半将处于休眠状态,直到错误的语句在前 16 个线程上执行完毕。然后这些线程将处于休眠状态,直到其他 16 个线程完成其分支。
正如你所看到的,糟糕的分支习惯会严重降低并行代码的速度,因为这两个语句都会在最坏的情况下执行。如果 warp 中的所有线程决定它们只需要其中一个语句,则完全跳过另一个并且不会发生延迟。
同步线程也不是一件简单的事情。你只能使用单个 SM 同步线程。SM 之外的所有内容在内核中都是不可同步的。你必须编写单独的内核并一个接一个地启动它们。