开始使用 cuda
CUDA 是用于其 GPU 的专有 NVIDIA 并行计算技术和编程语言。
GPU 是高度并行的机器,能够并行运行数千个轻量级线程。每个 GPU 线程的执行速度通常较慢,而且其上下文较小。另一方面,GPU 能够并行运行数千个线程,甚至更多并发运行(精确数量取决于实际的 GPU 模型)。CUDA 是专为 NVIDIA GPU 架构设计的 C++方言。但是,由于体系结构的不同,大多数算法都不能简单地从普通的 C++中复制粘贴 - 它们会运行,但速度很慢。
术语
- host - 指在该环境中运行的普通基于 CPU 的硬件和普通程序
- device - 指 CUDA 程序运行的特定 GPU。单个主机可以支持多个设备。
- kernel - 驻留在可以从主机代码调用的设备上的函数。
物理处理器结构
支持 CUDA 的 GPU 处理器具有以下物理结构:
- 芯片 - GPU 的整个处理器。有些 GPU 有两个。
- 流式多处理器 (SM) - 每个芯片最多包含~100 个 SM,具体取决于型号。每个 SM 几乎独立于另一个 SM 运行,仅使用全局存储器相互通信。
- CUDA 核心 - SM 的单个标量计算单元。它们的确切数量取决于架构。每个核心可以快速连续地处理几个并发执行的线程(类似于 CPU 中的超线程)。
此外,每个 SM 都具有一个或多个 warp 调度程序。每个调度程序将一条指令分派给多个 CUDA 核心。这有效地使 SM 在 32 宽 SIMD 模式下操作。
CUDA 执行模型
GPU 的物理结构直接影响内核在设备上的执行方式,以及如何在 CUDA 中对它们进行编程。使用调用配置调用内核,该调用配置指定生成多少并行线程。
- grid - 表示在内核调用时生成的所有线程。它被指定为一个或两个维数的块
- 块 - 是一组半独立的线程。每个块都分配给一个 SM。因此,块只能通过全局存储器进行通信。块不以任何方式同步。如果块太多,则一些块可能在其他块之后顺序执行。另一方面,如果资源允许,可以在同一个 SM 上运行多个块,但程序员无法从中发生这种情况(除了明显的性能提升)。
- 线程 - 由单个 CUDA 核心执行的标量序列指令。线程轻量级,具有最小的上下文,允许硬件快速交换它们。由于它们的数量,CUDA 线程运行时分配了几个寄存器,并且堆栈非常短(最好是没有!)。因此,CUDA 编译器倾向于内联所有函数调用以展平内核,使其仅包含静态跳转和循环。函数 ponter 调用和虚拟方法调用虽然在大多数较新的设备中受支持,但通常会产生重大的性能问题。
每个线程由块 blockIdx
中的块索引 blockIdx
和线程索引标识。任何正在运行的线程都可以随时检查这些数字,这是区分一个线程与另一个线程的唯一方法。
此外,线程被组织成 warp ,每个 warp 包含 32 个线程。单个 warp 中的线程在 SIMD fahsion 中执行完美同步。来自不同 warp 但在同一块中的线程可以按任何顺序执行,但可以由程序员强制同步。来自不同块的线程无法以任何方式同步或直接交互。
记忆组织
在正常的 CPU 编程中,内存组织通常对程序员是隐藏的。典型的程序就好像只有 RAM 一样。所有内存操作(例如管理寄存器,使用 L1-L2-L3 缓存,交换到磁盘等)都由编译器,操作系统或硬件本身处理。
CUDA 的情况并非如此。虽然较新的 GPU 模型部分地隐藏了负担,例如通过 CUDA 6 中的统一内存 ,但出于性能原因,仍然值得了解组织。基本的 CUDA 内存结构如下:
- 主机内存 - 常规 RAM。主要由主机代码使用,但较新的 GPU 型号也可以访问它。当内核访问主机内存时,GPU 必须通常通过 PCIe 连接器与主板通信,因此它相对较慢。
- 设备内存/全局内存 - GPU 的主要片外内存,可供所有线程使用。
- 共享内存 - 位于每个 SM 中,允许比全局更快的访问。共享内存对每个块都是私有的。单个块中的线程可以使用它进行通信。
- 寄存器 - 每个线程的最快,私有,无法寻址的内存。通常,这些不能用于通信,但是一些内在函数允许在经线内混洗它们的内容。
- 本地内存 -每个线程的私有内存是可寻址。这用于寄存器溢出和具有可变索引的本地数组。在物理上,它们存在于全局记忆中。
- 纹理内存,常量内存 - 全局内存的一部分,标记为内核不可变。这允许 GPU 使用专用缓存。
- L2 缓存 - 片上,可供所有线程使用。给定线程数量,每个缓存行的预期生命周期远低于 CPU。它主要用于辅助未对齐和部分随机的内存访问模式。
- L1 缓存 - 与共享内存位于同一空间。同样,在给定使用它的线程数量的情况下,数量相当小,因此不要指望数据会长时间停留在那里。可以禁用 L1 缓存。