5

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

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

我的合并问题是我必须对数据进行转置。我有很多数据,因此转置需要时间,如果可以的话,我宁愿将其用于其他事情。

我正在使用计算能力 2.0。

4

2 回答 2

9

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

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

现在关于您的实际问题,最好的办法可能是什么都不做,让缓存对其进行排序。这与您在共享内存中显式执行的方式相同,只是它由缓存硬件为您完成,并且不需要代码,这应该会使其速度稍快一些。唯一需要担心的是有足够的缓存可用,这样每个warp 可以有必要的32×32×4 字节= 4kbytes 的缓存用于字宽(例如float)或8kbytes 用于双重访问。这意味着限制同时处于活动状态的 warp 的数量是有益的,以防止它们破坏彼此的缓存行。

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

由于 16kB 缓存 / 48kB 共享内存的默认设置只允许每个 SM 四个 warp 一次执行转置(前提是您同时没有其他内存访问),因此选择 48kB 缓存 /使用cudaDeviceSetCacheConfig(). 较新的设备具有更大的缓存并提供更多不同的拆分以及选择使用超过 48kB 的共享内存。详细信息可以在链接的文档中找到。

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

于 2012-09-25T19:56:12.320 回答
7

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

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

如果你觉得一次 128 字节的缓存限制在惩罚你的代码,你可以尝试在非缓存模式下运行,这会将全局内存请求的粒度降低到一次 32 字节。如果你有一个分散的访问模式(没有很好地合并)这个选项可能会提供更好的性能。

于 2012-09-25T19:34:29.603 回答