CUDA - 合并内存访问和总线宽度

Ale*_*ias 4 memory cuda coalescing

所以我在CUDA中关于合并内存访问的想法是,warp中的线程应该访问连续的内存地址,因为这只会导致单个内存事务(每个地址上的值然后广播到线程)而不是多个那些将以连续方式执行的.

现在,我的总线宽度是48个字节.这意味着我可以在每个内存事务上传输48个字节,对吗?因此,为了充分利用总线,我需要能够一次读取48个字节(通过每个线程读取多个字节 - 内存事务由warp执行).但是,假设没有单个线程一次读取48个字节提供相同的优势(我假设我可以通过读取大小为48字节的结构一次读取48个字节)?

我的合并问题是我必须对数据进行转置.我有很多数据,所以转置它需要时间,如果可能的话我宁愿用于别的东西.

我正在使用Compute Capability 2.0.

ter*_*era 7

GPU的内存总线不仅仅是48字节宽(由于它不是2的幂,因此非常麻烦).相反,它由6个8字节(64位)的存储器通道组成.内存事务通常比通道宽度宽得多,以便利用内存的突发模式.良好的事务大小从64字节开始产生大小为8的突发,这与计算能力1.x设备上的半翘曲的16个32位字很好地匹配.

128字节宽的事务仍然快一点,并且匹配计算能力2.0(和更高)设备的warp-wide 32位字访问.高速缓存行也是128字节宽以匹配.请注意,所有这些访问必须在事务宽度的倍数上对齐,以便映射到单个内存事务.

现在关于你的实际问题,最好的办法可能是什么也不做,让缓存排序.这与您在共享内存中明确执行的操作方式相同,只是它由缓存硬件为您完成,并且不需要代码,这应该使其稍微快一些.唯一需要担心的是拥有足够的缓存,以便每个warp可以为字宽(例如float)提供必要的32×32×4字节= 4kbytes的缓存,或者为双访问提供8kbytes的缓存.这意味着限制同时处于活动状态的warp数量可能是有益的,以防止它们颠簸彼此的缓存行.

对于特殊优化,还可以使用像float2float4那样的矢量类型,因为所有支持CUDA的GPU都具有将8或16个字节映射到同一线程的加载和存储指令.然而,在计算能力2.0和更高版本上,我并没有看到在一般矩阵转置情况下使用它们的任何优势,因为它们甚至更多地增加了每个warp的缓存占用空间.

由于16kB缓存/ 48kB共享内存的默认设置只允许每个SM四个warp在任何时候执行转置(假设您没有同时没有其他内存访问),选择48kB缓存/可能是有益的16kB共享内存设置超过默认的16kB/48kB分割使用cudaDeviceSetCacheConfig().

为了完整起见,我还要提到计算能力3.0引入的warp shuffle指令允许在warp内交换寄存器数据,而无需通过缓存或共享内存.有关详细信息,请参阅" CUDA C编程指南"的附录B.14.
(请注意,如果没有本附录,则会出现一个版本的编程指南.因此,如果您的副本附录B.13中有其他内容,请通过提供的链接重新加载).


Rob*_*lla 6

出于合并的目的,如您所述,您应该专注于在warp访问连续位置中创建32个线程,最好是32字节或128字节对齐.除此之外,不要担心到DRAM内存的物理地址总线.存储器控制器主要由独立的分区组成,每个分区的宽度为64位.存储器控制器将尽快满足您从warp中出来的合并访问.对于访问int或float的完整warp(32个线程)的单个合并访问将需要检索128个字节,即物理总线上的多个事务到DRAM.当您在缓存模式下运行时,无论如何,您无法真正控制一次低于128字节的全局内存请求的粒度.

在单个事务中,不可能导致单个线程请求48个字节或类似的东西.即使在c代码级别,如果您认为一次访问整个数据结构,在机器代码级别它也会转换为一次读取32位或64位的指令.

如果您认为一次128字节的缓存限制会损害您的代码,您可以尝试以非缓存模式运行,这会将全局内存请求的粒度一次减少到32个字节.如果您具有分散的访问模式(未很好地合并),则此选项可以提供更好的性能.