我想了解银行冲突是如何发生的 如果我在全局内存中有一个256大小的数组,并且我在一个Block中有256个线程,我想将该数组复制到共享内存。因此每个线程复制一个元素。
shared_a[threadIdx.x]=global_a[threadIdx.x]
这个简单的行动会导致银行冲突吗?
现在假设数组的大小大于线程数,所以我现在用它来将全局内存复制到共享内存:
tid = threadIdx.x;
for(int i=0;tid+i<N;i+=blockDim.x)
shared_a[tid+i]=global_a[tid+i];
以上代码会导致银行冲突吗?
答案 0 :(得分:14)
检查此问题的最佳方法是使用“Compute Visual Profiler”分析您的代码;这附带了CUDA工具包。另外,GPU Gems 3中有一个很棒的部分 - “39.2.3避免银行冲突”。
“当同一个warp中的多个线程访问同一个bank时,除非warp的所有线程在同一个32位字内访问相同的地址,否则会发生bank冲突” - 首先是每个4字节宽的16个存储体。基本上,如果你在共享内存库中从相同的4字节读取内存中的任何一个线程读取内存,那么你将会遇到银行冲突和序列化等。
好的,这是你的第一个例子:
首先假设你的数组是例如类型 int (一个32位字)。您的代码将这些内容保存到共享内存中,跨越Kth线程保存到第K个内存库的任何一半扭曲。因此,例如,前半个warp的线程0将保存到第一个内存库中的shared_a[0]
,线程1将保存到shared_a[1]
,每个half warp有16个线程,这些线程映射到16个4byte bank 。在下一半warp中,第一个线程现在将其值保存到shared_a [16]中,再次位于第一个内存库中。因此,如果您使用4byte字,如int,float等,那么您的第一个示例将不会导致银行冲突。如果你使用一个1字节的字,比如char,在前半部分经线0,1,2和3都将它们的值保存到第一个共享存储区,这将导致存储体冲突。
第二个例子:
同样,这将取决于您使用的单词的大小,但是对于示例,我将使用4字节的单词。所以看着上半场的经线:
线程数= 32
N = 64
线程0:将写入0,31,63 线程1:将写入1,32
半warp中的所有线程并发执行,因此对共享内存的写入不应导致存储体冲突。我不得不仔细检查这个。
希望这会有所帮助,对不起这个巨大的回复!
答案 1 :(得分:3)
在两种情况下,线程都使用连续地址访问共享内存。它取决于共享内存的元素大小,但是线程扭曲对共享内存的连续访问不会导致“小”元素大小的存储体冲突。
使用NVIDIA Visual Profiler进行性能分析this code表明,对于小于32的元素大小和4的倍数(4,8,12,...,28),对共享内存的连续访问不会导致银行冲突。但是,元素大小为32会导致银行冲突。
Ljdawson的回答包含一些过时的信息:
...如果你使用一个1字节的字,比如char,在前半部分经线0,1,2和3都将它们的值保存到第一个共享存储器组,这将导致存储体冲突。 / p>
对于旧GPU,情况可能如此,但对于cc> = 2.x的最新GPU,由于广播机制(link),它们不会导致存储体冲突。以下引用来自CUDA C PROGRAMMING GUIDE (v8.0.61) G3.3. Shared Memory。
warp的共享内存请求不会在访问同一个32位字内任何地址的两个线程之间产生存储体冲突(即使这两个地址属于同一个存储区):在这种情况下,对于读取访问,该字被广播到请求线程(多个字可以在单个事务中广播),对于写访问,每个地址只由其中一个线程写入(该线程执行写操作未定义)。
这尤其意味着,如果按如下方式访问char数组,则不存在库冲突,例如:
extern __shared__ char shared[]; char data = shared[BaseIndex + tid];