关于手册中bank conflict的一些疑惑

在新版5.0的手册中,F.4.3章节中关于以64-bit宽度访问shared memory的说明中,举了一个例子:extern shared float shared;
double data = shared[BaseIndex + tid];

我的问题是:

  1. 比如,对于tid0,它读取的应该是bank0和bank1。那么tid1读取的是bank1和bank2吧? 不会是bank2和bank3吧?
  2. 对于这个例子,因为double是64bit宽,每次读取shared时读两个bank。因为每个tid在同一个circle读取的是不同的bank,所以没有conflict。这样理解对么?

另外对于F.4.3第二段的第一句话:A shared memory request for a warp does not generate a bank conflict between two threads that access any address within the same 32-bit word (even though the two addresses fall in the same bank)
我个人理解的是:即使一个warp中的任意两个thread同时访问同一个bank中的不同字(此处错误,应该为字节(感谢横扫千军版主))的地址,也不会发生冲突。
不知道这样理解对不对。

求各位高手出招啊

楼主你好。

手册有大量错误存在,而且存在好久都无人修正。今日你看到的就是一个。

这个例子实际上是错误的,因为:
extern shared float shared;
double data = shared[BaseIndex + tid]; //在这里会导致一个普通的LDS访问,而不是LDS.64
因为你的数组被定义为float, 而不是double, 实际上编译器会执行一次32位存取,然后cast这个32bit的float值为64bit的double, (通过fermi的F2F指令,当然,你可以不关心这个)。

因为这个例子是错误的,他执行的自然总是32位读取,而不是64位。因为你假设的前提(64位读取)实际上在此例子中并不存在,所以你的结论自然无需讨论。

对于您的后一个个问题,您的确可以理解为在一shared memory bank为4B宽的fermi硬件上,硬件有能力将1个4B值读取,然后分散给多个线程(中的寄存器),以及将来自多个线程的值,组合为1个4B, 然后写入。

您的这个理解是正确的,但说法需要商榷:“我个人理解的是:即使一个warp中的任意两个thread同时访问同一个bank中的不同字的地址,也不会发生冲突。 不知道这样理解对不对。”

改为: 不同字节(而不是不同字,字(word)有其他含义)。即可正确。

:slight_smile:

EDIT,更新于原帖发表16个小时后:
最后一段是接楼主的原文中的英文部分说的,这里暗含了“这些bytes在1个4B范围内的“含义,而不是指任意的bytes。请其他应用者不要断章义。

此外,如果对楼主你的例子修改一下,改成:

extern shared double shared;
double data = shared[BaseIndex + tid];

那么这个访问,根据手册的说法,的确是无冲突的。但具体的原因我真的不能知道,因为NV没说。可能是硬件上的某种设计导致了这种情况。(例如民间消息说,有32个真实的banks, 但只有16组元素宽度的LSU(Load Store Unit), 再加上LSU的奇特的运行频率(据说一部分电路是倍频的,一部分运行于基频),以及LSU对存取操作的组合/拆分等因素,一起才导致的)(此来源信息完全不可靠,仅供参考)。

建议楼主记住结论(的确无bank conflict的,不仅仅是手册说,实际应用中也如此)。同时建议其他版主给出原厂的解释。

这是对fermi的阐述。因为你刚才询问的手册章节也是谈论fermi的。

我实在不知道该说什么了,感觉就像是攒了一个月生活费想去买辆自行车,结果店家直接送了一辆法拉利!

BZ你太牛逼了!!!

才看了一遍,继续第二遍。

太激动了

能为您效劳是我的荣幸。:slight_smile:

万能的BZ,我又看了一遍你修改过的例子,又有点小疑惑。

比如,

  1. 把shared定义为double,那么按照手册上“Each bank has a bandwidth of 32 bits per two clock cycles ”的说法,那么shared的每个元素是不是都需要4个circle才能完成存取呢?
  2. 如果是double类型的话,每个warp里32个线程就需要64个bank (因为一个bank只占了半个double),那么bank0-31和bank32-63是重合的。如果这时候写入的话,bank里的数据就成了随机数了吧?

另外,NV提供的linux下的debug工具可以查看每次运算执行了几次circle么?

我不光是cuda菜鸟,C语言其实也是才入门,也没学过数据结构,希望BZ不要觉得烦啊 :smiley:

(1)在2.x上,有2种频率,SM运行在较低的频率上,而SP和其他的一些单元运行在倍频上。
Shared memory也运行在基本频率上,在这个基本频率上,它的32个banks, 可以在一个时钟内,给出32个4B数据。也就是128B/cycle. 但是如果算到SP频率上,就只有64B/cycle。

那么回到你的问题,如果是32个线程,每个8B, 是需要256B的总数据量的,那么自然是需要4个时钟周期的(请注意这个周期针对是SP的那种倍频说的,如果算基本频率,那么需要2个)。

(2)这32个线程的double, 来自于32个banks. 每个banks连续提供了2次数据。你的描述以及后续的问法,表明你对这个概念不理解,我举个简单的比喻,你可以理解成32挺机枪(banks就是那黑洞洞的机枪口),每次大家发出1发子弹(4B),double就相当于双发的子弹(8B), 依然还是这32挺机枪,只不过每个洞口(bank)里连续2次出来而已(假设此机枪支持连续出来2次)。 因为我重新对你解释了概念,所以你原来的概念已经发生了改变,不再存在。因为你原来的概念不再存在,所以基于此概念的问题,以及它的解释也不再需要存在。

(3)我不懂Linux(R), 也无法为此提供建议。我建议你咨询其他会员和版主。

对了,你的cycle拼写有误,你拼成了circle。建议更正。

再次感谢!

第一个问题我明白了,但是对于第二个问题,我还是问题想问一下。

您在第二个问题的回复中说到了”发射两次“,这也让我想到了手册上介绍64bit宽度读取时所用的half-warp一词,所以我也明白了。 但是仔细一想,bank应该是一个一维的存储空间,那么如果一个bank连续地读两次,那么这个double数的高低位不就重复了么?如果连续写两次,那么写入的32bit就被覆盖掉了啊。

还烦请BZ帮忙解惑啊。 顺便问一下,BZ您都是从哪儿学到的这些呢?有什么推荐的教材么? 十分感谢!

我看了下CUDA4.2的F4.3,说了如果warp中的两个线程访问到相同32bit中的某个byte时,是不会有bank conflict,会被一次广播操作完成。而如果访问的是不同32bit并且bank相同,则会有冲突产生。
64bit访问的时候是以half-warp,因此也不会产生bank conflict

这里的“发射2次子弹”是为了你能详细的理解,对应的,是一个bank连续给出归属于这个bank的2个32-bit数据。

“bank应该是一个一维的存储空间,那么如果一个bank连续地读两次,那么这个double数的高低位不就重复了么?”
–说下这个,1维,与,只有1个存储空间,是不同的概念。我们常见的int dog也是一维的,但里面不是只有1个元素的存储空间。与之对应,banks是能存放一定地址规律的元素(其实这个是地址转换后的结果),而不是只能存放一个元素。

说下32个banks, 用存放float/int之类大小为4B的元素为例,有32个banks(假设编号是从1开始的), 以及你有地址上连续的100个元素(依然假设编号从1开始,为了方便理解),如果1号元素在bank 1, 那么 11号元素就在bank 11, 那么22号元素就在bank 22, 32号元素就在bank 32。然后再有地址上继续的,就转圈回来了,33号元素也在bank 1。

正因为如此,如果你的32个线程同时访问的32个int/float都是正好来自这32个banks, 那么这32个banks都在忙碌,你也一次能得到这一批元素;但是,如果你32个线程,需要的元素来自31个banks,
那么必然有1个bank是空闲的,而另外一个bank则要吐出2个元素给你(不考虑特殊情况),这样就降低了效率。因为要等待这一批元素整体的读出,需要第一次使用31个bank(剩1个空闲), 第二次则前31个干过活动banks中的1个bank, 继续再干一次。这样就导致了需要2次。这样做,就叫冲突的访问,因为冲突导致有的bank有时不能工作,以及有的bank有时需要多次工作,所以才是效率降低的原因。

因为重新对你阐述了你脑海中的“一维的存放空间和一个存储空间”的区别,所以你的概念发生了变化。因为你的前置概念发生了变化,所以你的问题的前提不再存在,所以问题也不再存在。因为问题不再存在,所以也无需回答。

:slight_smile:

非常感谢!我对您的回复的理解是:

  1. bank的最小操作单位是byte,而不是word;
  2. 如果两个thread同时盯上了同一个byte,就会产生广播;如果盯上了同一个bank(即word)中不同的byte,那么就会冲突。

不知道理解的对不对,还望指教!

(1)不是bank的最小单位是byte, 而是任何你的卡任何最小访存单位都是byte(当然,有支持小于byte寻址能力的CPU存在。例如8051)。

(2)这里楼主你又糊涂了,上文你还是明白的。同一个word(4B)里的不同byte,被同一个warp里的多个线程使用,是没有bank conflict的。(是的。你前文自己还这么说过!)

建议浣熊每次洗完食物再吃。

您的回复总是这么给力!

额,我二了。。。

可是总版说“如果访问的是不同32bit并且bank相同,则会有冲突产生”,该如何理解这句话呢?

:dizzy:

这句话和上贴无矛盾,他说的是,如果warp里多个线程要用到多个不同的4B, 需要从一个bank里出来的,那么则会冲突。而不是说,同一个4B里的不同bytes, 会冲突。请仔细注意识别!

其实是一个简单的事实:
1:多个线程访问同一个bank里面的4B长度的类型,如int,float,会发生一次广播,并无bank conflict。

2:多个线程访问存于同一个bank里面的1B长度的类型(一共有4个1B的类型,比如char),无论是同一个该1B类型,还是不同的该1B类型,都不会发生bank conflict,应该也是广播实现的。

第二种情况,从bank那里拿到,没有bank conflict,这个很好理解,因为只涉及一个bank,读一次就4B内容都拿到了。至于如何再转成所需的1B类型,这个我不清楚,看作是硬件提供的福利吧。

懂了! 这就是你之前说过的有的枪罢工了,有的枪就只好多出点力了。

感觉豁然开朗啊,哈哈哈。

这回没错了吧

嗯,多谢啊!

我建议你针对整篇来评论,而不是节选。

当然,对于前一句“他说的是,如果warp里多个线程要用到多个不同的4B, 需要从一个bank里出来的,那么则会冲突”的评论还是没错的。:slight_smile:

好吧,我给你个形象的表示
假设你的shared memory里面有个数组int array[64],那么bank的划分是
array[0] array[1] array[2] … array[31] array[32] …
bank0 bank1 bank2 … bank31 bank0 …

现在假设你一个warp里面的线程thread0和thread15, 访问array[0]那么不会有bank conflict,array[0]中的数据会以广播的方式给thread0和thread15

但是,如果你的thread0访问array[0]而thread15要访问array[32],那么就会出现bank conflict,因为它们都需要通过bank0来访问共享内存