大多数设备端数据访问都是从全局内存开始的,并且多数GPU应用程序容易受内存带宽的限制。因此,最大限度的利用全局内存带宽是调控核函数性能的基本。
对齐与合并访问
如图,所有的应用程序数据最初存在于DRAM上,也就是物理设备内存上。核函数的内存请求通常是在DRAM设备和片上内存间以128字节或32字节内存事务来实现的。
所有对全局内存的访问都会通过二级缓存,也有许多访问会通过一级缓存,这取决于访问类型和GPU架构。如果都用到,就是128字节内存事务;如果只用了二级缓存,那么该内存访问是由一个32字节的内存事务实现的。
内存事务(Memory Transaction)指的是对内存进行的读取或写入操作。在GPU编程中,内存事务通常指的是对全局内存或共享内存的读取或写入操作,这些操作可以由单个线程或线程组(线程束)执行。
对齐的概念与C++类中的内存对齐概念相似,为了避免重复的读取数据,需要将数据在内存中对齐。
合并是指将多个内存事务合并为一个事务,合并通常发生在多个线程或线程束同时访问连续内存地址时。如果这些访问可以被合并为一个更大的内存事务,GPU可以更有效地利用内存系统的带宽和并行性。合并通常由GPU自动完成。
全局内存读取
在SM中,数据通过以下3种缓存/缓冲路径进行传输,具体使用哪种方式取决于引用了哪种类型的设备内存
- 一级和二级缓存
- 常量缓存
- 只读缓存
一级和二级缓存是默认路径。
CPU一级缓存和GPU一级缓存的差异
CPU一级缓存优化了时间和空间局部性。GPU一级缓存是专门为了空间局部性而不是为了时间局部性设计的。频繁访问一个一级缓存的内存位置不会增加数据留在缓存中的概率。
时间局部性和空间局部性
时间局部性认为如果一个数据位置被引用,那么该数据在较短的时间周期内很可能会被再次引用,随着时间,该数据被引用的可能性逐渐降低。空间局部性认为,如果一个位置被引用,则附近的位置也可能会被引用。
全局内存写入
一级缓存不能用在费米或开普勒GPU上进行存储操作,在发送到设备内存之前存储操作只通过二级缓存。
结构体数组和数组结构体
数组结构体(AoS)
struct S{
float x;
float y;
};
struct S myAoS[N];
结构体数组(SoA)
struct S{
float x[N];
float y[N];
};
画个图说明这两种结构体存储数据的区别
用AOS模式在GPU上存储一个只使用x字段的应用程序,将导致50%的带宽损失。y值在32字节或128字节缓存行上隐式的被加载。AOS模式也浪费了二级缓存的空间在不需要的y值上。
相比之下,SOA模式存储数据,可以更好的提高资源的利用率。
在并行编程中,更倾向于使用SOA模式。因为数据元素是为全局内存的有效合并访问而预先准备好的,而被相同内存操作引用的同字段数据元素在存储时是彼此相邻的。