什么是银行冲突?(进行Cuda/OpenCL编程)

118
我一直在阅读CUDA和OpenCL的编程指南,但是我无法理解什么是银行冲突。他们只是简单地介绍了如何解决这个问题,没有详细解释这个主题本身。有人可以帮我理解吗?我对于帮助是在CUDA/OpenCL的上下文中还是在计算机科学中的银行冲突方面都没有偏好。
5个回答

134
对于Nvidia(以及AMD)的GPU来说,本地内存被分成了多个内存块。每个块一次只能访问一个数据集,所以如果半个warp试图从/向同一个块加载/存储数据,访问就必须被串行化(这就是一个内存块冲突)。对于GT200 GPU,有16个块(Fermi有32个块),AMD GPU有16或32个块(57xx或更高:32个,低于57xx:16个),这些块以32位的粒度交错排列(所以字节0-3在块1中,4-7在块2中,...,64-69在块1中,以此类推)。为了更好地可视化,它基本上是这样的:
Bank    |      1      |      2      |      3      |     ...     |      16     |
Address |  0  1  2  3 |  4  5  6  7 |  8  9 10 11 |     ...     | 60 61 62 63 |
Address | 64 65 66 67 | 68 69 70 71 | 72 73 74 75 |     ...     |     ...     |
...

所以,如果半个warp中的每个线程都访问连续的32位值,就不会发生bank冲突。
这个规则的一个例外(每个线程必须访问自己的bank)是广播:如果所有线程都访问相同的地址,那么该值只会被读取一次并广播给所有线程(对于GT200,必须是半个warp中的所有线程访问相同的地址,我记得Fermi和AMD的GPU可以对任意数量的线程访问相同的值进行广播)。

4
感谢您提供的图像和解释,我不知道广播是什么,这似乎是一条重要的信息 :)如何验证我的负载和存储操作不会在共享内存中引起银行冲突?我是否需要以某种方式获取汇编代码或者还有其他方法? - smuggledPancakes
4
由于银行冲突的发生是在运行时确定的(这意味着编译器并不知道它,毕竟大多数地址是在运行时生成的),获取编译版本帮助不大。我通常采用老式的方法,拿起笔和纸,开始考虑我的代码在何处存储。毕竟,管理银行冲突发生的规则并不那么复杂。否则,您可以使用 NVIDIA OpenCL 分析器(应该与 SDK 捆绑在一起,如果我没记错的话)。我认为它有一个用于warp序列化的计数器。 - Grizzly
1
感谢指出warp序列化。计算分析器附带的自述文件之一中提到了这一点。 - smuggledPancakes
1
抱歉,忽略上面的评论,由于某些原因我无法重新编辑它。无论如何,我在计算分析器的自述文件中找到了这个信息:“warp_serialize:在地址冲突时序列化到共享内存或常量内存的线程warp数量。” 这很棒,我可以通过查看分析器输出轻松地看到是否存在冲突。你如何在纸上确定是否有银行冲突?你是从任何示例或教程中学到的吗? - smuggledPancakes
1
正如我所说,从地址到银行的映射相对简单,因此很容易确定哪些访问属于哪个银行,从而判断是否存在银行冲突。这篇论文仅针对更多的冲突访问模式,如果没有这篇论文,我无法完成。 - Grizzly

19

可以并行访问的共享内存被分成了模块(也称为bank)。如果两个内存地址在同一个bank中,那么就会发生bank冲突,此时访问将串行进行,失去了并行访问的优势。


这与半个warp想要存储或加载内存有关吗?16个线程将尝试进行内存事务,因此使用多个线程访问同一银行会导致序列化处理?另外,如何确保您没有在同一银行中存储/加载数据? - smuggledPancakes

16
简单地说,银行冲突是指任何内存访问模式无法将IO(输入/输出)分布在内存系统中可用的各个bank之间的情况。下面的例子阐述了这一概念:假设我们有一个由整数组成的二维512x512数组,并且我们的DRAM或内存系统中有512个bank。默认情况下,数组数据会以这样的方式布局:arr [0] [0]进入 bank0,arr [0] [1]进入bank1,arr [0] [2]进入bank2 ... arr [0] [511]进入bank511。一般来说,arr [x] [y]占用bank编号y。如果某些代码(如下所示)以列优先的方式访问数据,即在保持y不变的同时更改x,则最终结果将是所有连续的内存访问都命中同一个bank——因此出现了银行冲突。
int arr[512][512];
  for ( j = 0; j < 512; j++ ) // outer loop
    for ( i = 0; i < 512; i++ ) // inner loop
       arr[i][j] = 2 * arr[i][j]; // column major processing

通常,编译器通过对数组进行缓冲或使用素数个元素来避免这种问题。


12

2
请注意,仅链接答案是不被鼓励的,SO答案应该是寻找解决方案的终点(而不是另一个参考站点,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的摘要,将链接作为参考。 - kleopatra
请详细说明链接,以更好地帮助提问者。 - Peter Foti
3
这个视频非常有帮助!我不知道为什么会有反对投票!这是非常好的内容!+1 - Gabriel

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接