据我所知,共享内存分为多个存储区,多个线程对同一个存储区内的单个数据元素的访问会导致冲突(或广播)。
目前我分配了一个相当大的数组,它在概念上代表了几对两个矩阵:
__shared__ float A[34*N]
其中N
是对的数量,一对的前16个浮点数是一个矩阵,后面的18个浮点数是第二个。
问题是,对第一个矩阵的访问是无冲突的,但对第二个矩阵的访问存在冲突。这些冲突是不可避免的,但是,我的想法是,因为第二个矩阵是18,所有未来的矩阵都会错位到银行,因此会发生比必要更多的冲突。
这是真的,如果是这样我怎么能避免呢?
每次我分配共享内存时,它是从新银行开始的吗?我可以这样做
__shared__ Apair1[34]
__shared__ Apair2[34]
...
有什么想法吗?
由于
答案 0 :(得分:5)
如果您的矩阵对是连续存储的,并且如果您通过线程索引线性访问元素,那么您将不会有共享内存库冲突。
换句话说,如果你有:
A[0] <- mat1 element1
A[1] <- mat1 element2
A[2] <- mat1 element3
A[15] <- mat1 element16
A[16] <- mat2 element1
A[17] <- mat2 element2
A[33] <- mat2 element18
您可以使用以下方式访问此内容:
float element;
element = A[pairindex * 34 + matindex * 16 + threadIdx.x];
然后相邻的线程正在访问矩阵中的相邻元素,并且没有冲突。
回应您的评论(如下),您的理解似乎错误。确实存在16个存储体(在当代中,在下一代中为32个,费米),但是连续的32位字存在于连续的存储体中,即地址空间在存储体之间交错。这意味着,如果你总是有一个可以分解为x + threadIdx.x
的数组索引(其中x
不依赖于threadIdx.x,或者至少在16个线程的组中是恒定的)你将不会有银行冲突。
当你沿阵列进一步访问矩阵时,你仍然可以在一个连续的块中访问它们,因此你不会有银行冲突。只有当您开始访问非相邻元素时才会发生银行冲突。
SDK中的 reduction 示例通过从简单的实现构建到优化的实现,很好地说明了银行冲突,可能值得一看。
答案 1 :(得分:2)
设置存储区,使得每个连续的32位位于下一个存储区中。因此,如果声明一个4字节浮点数组,则数组中的每个后续浮点数将位于下一个存储区中(模16或32,具体取决于您的体系结构)。我假设您使用的是计算能力1.x,因此您有一个宽度为16的存储区。
如果您有18和16的数组,事情可能很有趣。您可以通过将其声明为
来避免16x16阵列中的存储体冲突__shared__ float sixteen[16][16+1]
使用threadIdx.x访问转置元素时避免了银行冲突(正如我假设您在发生冲突时所做的那样)。当访问16x16矩阵的第一行中的元素时,它们都将驻留在第一个库中。你想要做的是在一个连续的银行中拥有这些。 Padding为您做到这一点。您可以像以前一样处理数组,如同16个[row] [column],或类似于扁平矩阵,如果需要,则为16行[row *(16 + 1)+ column]。
对于18x18的情况,当在转置中进行访问时,你的步伐是均匀的。答案再次是1。
__shared__ float eighteens[18][18+1]
现在,当您在转置中访问时(比如访问第一列中的元素),它将以(18 + 1)%16 = 3的形式访问,您将访问第3,6,9,12行, 15,2,5,8等,所以你不应该有冲突。
由于具有大小为18的矩阵而导致的特定对齐偏移不是问题,因为阵列的起点没有区别,它只是您访问它的顺序。如果你想展平我上面提到的数组,并将它们合并为1,那就没问题了,只要你以类似的方式访问它们。