技术开发 频道

CUDA技巧与经验 global&bank conflict

  【IT168 文档】访问global虽然仍然需要最多数百个时钟周期,但是总的来说比cpu的内存还是快多了...如果使用得当的话。CUDA是符合SIMD-PRAM模型的,也就是n台功能相同的处理机(block看成处理机,每个thread看成处理机的一部分更好些),一个容量无限大的共享处理器M。其中M就是global memory,虽然在CUDA中也是有限的,但是比起可怜的shared还是多很多了。

  global作为共享存储器,最重要的功能就是用来进行block之间的通信。这里能不能叫做通信其实有问题,因为block之间实际上没有通信...CUDA上的通信实际上就是刷新,运行完一个kernel,把所有的需要与其他block交换的数据都写进global,然后再launch一个kernel读入上次生成的数据...如此往复循环。

  如果kernel的输入与输出是同一个矩阵,就是一个原地(in-place)操作,下个kernel还是读取这个矩阵。

  如果输入矩阵与输出矩阵不同,而且需要反复操作这两个矩阵,就构成了一个乒乓操作,例如第一次kernel使用ping为输入,pong为输出,下次就用pong输入,ping输出。这种方法在迭代等数值计算中经常使用。

  texure也是全局存储器,速度比global还要更快,但是我不推荐大家在GPGPU中使用texture。这是因为texture是只读的,array中的数据不随对应的矩阵改变而改变,需要反复绑定,而绑定占用的时间不一定比使用texture带来的提高小。大多数情况下,使用global + shared比使用texture要快。当然,在需要大块的只读数据如查找表,索引表时可以使用texture。

  关于bankconflict

  简单的说,矩阵中的数据是按照bank存储的,第i个数据存储在第i%16个bank中。一个block要访问shared memory,只要能够保证以其中相邻的16个线程一组访问thread,每个线程与bank是一一对应就不会产生bank conflict。否则会产生bankconflict,访存时间成倍增加,增加的倍数由一个bank最多被多少个thread同时访问决定。有一种极端情况,就是所有的16个thread同时访问同一bank时反而只需要一个访问周期,此时产生了一次广播。

  下面有一些小技巧可以避免bank conflict 或者提高global存储器的访问速度

  1. 尽量按行操作,需要按列操作时可以先对矩阵进行转置
  2. 划分子问题时,使每个block处理的问题宽度恰好为16的整数倍,使得访存可以按照 s_data[tid]=i_data[tid]的形式进行
  3. 使用对齐的数据格式,尽量使用nvidia定义的格式如float3,int2等,这些格式本身已经对齐。
  4. 当要处理的矩阵宽度不是16的整数倍时,将其补为16的整数倍,或者用malloctopitch而不是malloc。
  5. 利用广播,例如s_odata[tid] = tid%16 < 8 ? s_idata[tid] : s_idata[15];会产生8路的块访问冲突

  而用:s_odata[tid]=s_idata[15];s_odata[tid]= tid%16 < 8 ? s_idata[tid] : s_data[tid]; 则不会产生块访问冲突

0
相关文章