第五章 CUDA 存储器
《大规模并行处理器编程实战》学习,其他章节关注专栏 CUDA C
CUDA C 编程友情链接:
- 第三章 CUDA 简介-CUDA C编程向量加法
- 第四章 CUDA数据并行执行模型
- 第五章 CUDA 存储器
- 第六章 CUDA性能优化(内附原书链接)
- 核函数:CUDA编程入门(一)-以图片运算看线程的组织和核函数的使用
- 拓展:CUDA卷积计算及其优化——以一维卷积为例
线程处理数据时需要先将数据从 host copy 到 device 的全局存储器中,然后利用 block ID 和 thread ID 决定其需要处理的数据位置,从而进行并行计算。kernel 在计算时,需要和存储器(全局存储器)进行大量的互动,这种存储器通常使用动态随机访问存储器(Dynamic Random Access Memory, DRAM),在访问时时延有时会特别大,因此 CUDA 提供了大量访问存储器的方法,这种方法消除了对全局存储器的访问,从而提高访存速度,提高 CUDA kernel 处理速度。
5.1 存储器访问效率的重要性
CGMA(Compute to Global Memory Access)指在 CUDA 程序在某一区域内每次访问全局存储器时,执行浮点运算的次数。CUDA 比值能较准确的体现 CUDA 中 kernel 函数的性能。
此时的 CGMA = 1:1 = 1.0
当全局存储器的带宽是 200GB/s,单精度浮点数为4字节,则单精度浮点数的读带宽为 200GB/4B = 50 GFLOPS,当 CGMA = 1.0 时,则单精度浮点数的操作不能超过 50 GFLOPS。
5.2 CUDA 设备存储器的类型
- 全局存储器(主机交互)
- 常数存储器(只读)
- 寄存器和共享存储器,即片上存储器寄存器只能被单个线程访问,共享存储器可以被线程块中的所有线程访问。
两种不同处理架构
不同变量在定义其作用域的时候,有不同的声明方法:
注:自动数组变量存储在全局存储器中
5.3 减少全局存储器流量的一种策略
全局存储器访问慢,而共享存储器访问快。将数据划分为多个子集,称为块(tile),进行块的存取,减少访存(全局)次数,提高效率。
对经常访问全局存储器点可以驻留在片上存储器中,可以减少访全局存,但过多的驻留会导致片上存储器被大量占用,因此需要用栅栏同步进行同步,减少驻留时间。
5.4 分块矩阵乘法的 kernel 函数
对于原始的矩阵乘法,可以进行分步运算。
对于每一阶段,
- 每个线程先从全局存储器取所需块数据到共享存储器,每1个线程取2个位置(以2*2大小为单元进行矩阵乘);
- 将取得数据进行sum of product积之和运算,得到此2*2单元的矩阵乘积。
- 如图,红色表示 P 0 , 0 P_{0,0} P0,0线程的操作,将红色区域进行积之和,其取 M 0 , 0 M_{0,0} M0,0和 M 0 , 1 M_{0,1} M0,1的数据,N矩阵的取值交给紫色。其他颜色同理,因为同一数据M或N同时被2*2范围内的两个线程使用,因此由两个线程在同一阶段进行分别取值,共享数据。
[
5.5 存储器——限制并行性的一个因素
每个SM中能分配的寄存器和共享存储器都是有限的,且是以线程块为单位进行分配的。若可容纳线程块*所需寄存器/存储器每块>SM的最大容量,则会以块为单位较少每个SM中的运行线程块数量。若存储器/寄存器可容纳线程数大于SM的最大容纳线程数,则SM的固有最大线程数成为了限制速度的因素。
编程时根据获取的硬件信息,动态的确定共享存储器的使用量,可以提高GPU利用率,加速计算。因此对矩阵乘法中的共享寄存器声明进行修改:
在启动Kernel时,也能进行存储器大小设定: