在CUDA中,什么是内存合并,如何实现?


77

什么是CUDA全局内存事务中的“合并”?即使阅读了CUDA指南,我也听不懂。怎么做?在CUDA编程指南矩阵示例中,逐行访问矩阵称为“合并”或由col ..称为colesced?哪个正确,为什么?

Answers:


152

此信息可能仅适用于计算能力1.x或cuda 2.0。较新的体系结构和cuda 3.0具有更复杂的全局内存访问,实际上,这些芯片甚至都没有分析“成组全局负载”。

同样,此逻辑可以应用于共享内存,以避免存储体冲突。


合并的内存事务是其中半warp中的所有线程同时访问全局内存的事务。这太简单了,但是正确的方法是让连续的线程访问连续的内存地址。

因此,如果线程0、1、2和3读取全局内存0x0、0x4、0x8和0xc,则它应为合并读取。

在矩阵示例中,请记住要使矩阵线性驻留在内存中。您可以根据需要执行此操作,并且对内存的访问应反映矩阵的布局方式。因此,下面的3x4矩阵

0 1 2 3
4 5 6 7
8 9 a b

可以这样一行一行地完成,这样(r,c)映射到内存(r * 4 + c)

0 1 2 3 4 5 6 7 8 9 a b

假设您需要访问一次element,并说您有四个线程。哪些线程将用于哪个元素?可能是

thread 0:  0, 1, 2
thread 1:  3, 4, 5
thread 2:  6, 7, 8
thread 3:  9, a, b

要么

thread 0:  0, 4, 8
thread 1:  1, 5, 9
thread 2:  2, 6, a
thread 3:  3, 7, b

哪个更好?哪些将导致合并的读取,而哪些将不会导致读取?

无论哪种方式,每个线程都会进行三个访问。让我们看一下第一次访问,看看线程是否连续访问内存。在第一个选项中,第一个访问是0、3、6、9。不连续,也不合并。第二个选项是0、1、2、3。合并!好极了!

最好的方法可能是编写您的内核,然后对其进行概要分析,以查看您是否具有未分配的全局负载和存储。


感谢您的解释,以了解哪个线程访问哪个元素。目前,我有第一个选择(thread 0: 0, 1, 2 etc...),所以我现在正在寻找更好的选择:-)
蒂姆

@jmilloy-我想问一下如何对内核进行概要分析,以查看非批量全局加载和存储。
muradin 2013年

1
@muradin可以使用Visual Profiler吗?developer.nvidia.com/nvidia-visual-profiler
jmilloy

@jmilloy-由于我在非图形环境中工作,因此我在命令行模式下搜索并找到了nvprof。但是当我想运行它时,出现错误:nvprof无法加载libcuda.so.1,没有这样的文件或目录!你知道为什么吗?
muradin 2013年

@jmilloy:您好,非常好的例子!谢谢!我想问您,当您说可以运行探查器以查看是否已合并访问时,如何执行呢?对于xample,运行:nvprof --metrics gld_efficiency吗?越高越好?
乔治

11

内存合并是一种允许最佳使用全局内存带宽的技术。即,当运行相同指令的并行线程访问全局存储器中的连续位置时,将获得最有利的访问模式。

在此处输入图片说明

上图中的示例有助于说明合并的安排:

在图(a)中,以线性方式存储长度为m的n个矢量。向量j的元素iv j i表示。GPU内核中的每个线程都分配给一个m长度的向量。CUDA中的线程被分组在一个块数组中,GPU中的每个线程都有一个唯一的ID,可以将其定义为,其中代表块尺寸,表示块索引并且是每个块中的线程索引。 indx=bd*bx+txbdbxtx

垂直箭头表示并行线程访问每个向量的第一部分的情况,即存储器的地址0,m2m ...。如图(a)所示,在这种情况下,存储器访问不是连续的。通过将这些地址之间的间隙归零(如上图所示的红色箭头),可以合并内存访问。

但是,这里的问题有点棘手,因为每个GPU块的驻留线程的允许大小限制为bd。因此,可以通过bd以连续顺序存储第一矢量的第一元素,然后存储第二bd矢量的第一元素等来完成合并的数据排列。其余向量元素以类似方式存储,如图(b)所示。如果n(向量数)不是的因数bd,则需要用一些琐碎的值(例如0)填充最后一块中的剩余数据。

在直线的数据存储在图(a),成分。 (0≤<)载体的INDX (0≤ INDX < Ñ)由寻址m × indx +i; 图(b)中合并存储模式中的相同组件的地址为

(m × bd) ixC + bd × ixB + ixA

其中ixC = floor[(m.indx + j )/(m.bd)]= bxixB = jixA = mod(indx,bd) = tx

总而言之,在存储许多大小为m的向量的示例中,线性索引根据以下内容映射到合并索引:

m.indx +i −→ m.bd.bx +i .bd +tx

这种数据重排会导致GPU全局内存的显着更高的内存带宽。


来源:“非线性有限元变形分析中基于GPU的计算加速。” 国际生物医学工程数值方法杂志(2013年)。


9

如果块中的线程正在访问连续的全局内存位置,则所有访问都将被硬件组合成单个请求(或合并在一起)。在矩阵示例中,行中的矩阵元素线性排列,然后是下一行,依此类推。例如,对于一个块中的2x2矩阵和2个线程,存储位置安排为:

(0,0)(0,1)(1,0)(1,1)

在行访问中,线程1访问无法合并的(0,0)和(1,0)。在列访问中,线程1访问(0,0)和(0,1),它们可以合并,因为它们是相邻的。


7
简洁明了,但是..请记住,合并不是线程1进行的两次串行访问,而是线程1和线程2并行进行的同时访问。在您的行访问示例中,如果线程1访问(0,0)和(1,0),则我假设线程2正在访问(0,1)和(1,1)。因此,第一个并行访问是1:(0,0)和2:(0,1)->合并!
jmilloy

2

合并的标准在CUDA 3.2编程指南的G.3.2节中有很好的记录。简短版本如下:warp中的线程必须按顺序访问内存,并且被访问的字应> = 32位。此外,warp访问的基址应分别为32位,64位和128位访问的64位,128位或256字节对齐。

Tesla2和Fermi硬件可以很好地完成8位和16位访问的合并,但是如果您想要峰值带宽,最好避免使用它们。

请注意,尽管对Tesla2和Fermi硬件进行了改进,但结合起来已过时了。即使在Tesla2或Fermi类硬件上,未能合并全局内存事务也可能导致性能下降2倍。(在Fermi类硬件上,只有在启用ECC的情况下,这才是正确的。连续但不中断的内存事务对Fermi造成了20%的损失。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.