我刚刚开始使用CUDA进行编码,我试图了解线程执行和内存访问的概念,以便充分利用GPU。我通读了CUDA最佳实践指南,CUDA示例书和几篇帖子。我还发现马克哈里斯的reduction example非常有趣和有用,但尽管所有的信息我都对这些细节感到困惑。
假设我们有一个大的二维数组(N * M),我们在其上进行逐列操作。我将数组拆分为块,以便每个块具有多个32的倍数的线程(所有线程都适合几个warp)。每个块中的第一个线程分配额外的内存(初始数组的副本,但仅限于其自身维度的大小)并使用_ shared _ 变量共享指针,以便所有线程相同的块可以访问相同的内存。由于线程数是32的倍数,所以应该是内存以便在单次读取中访问。但是,我需要在内存块周围有一个额外的填充,一个边框,这样我的数组的宽度就变成(32 * x)+2列。边界来自分解大数组,因此我有一个重叠区域,其中临时可用其邻居的副本。
同步内存访问:
想象一下,块的线程正在访问本地内存块
1 int x = threadIdx.x;
2
3 for (int y = 0; y < height; y++)
4 {
5 double value_centre = array[y*width + x+1]; // remeber we have the border so we need an offset of + 1
6 double value_left = array[y*width + x ]; // hence the left element is at x
7 double value_right = array[y*width + x+2]; // and the right element at x+2
8
9 // .. do something
10 }
现在,我的理解是因为我确实有一个不可避免的偏移量(+ 1,+ 2),每个warp和每个赋值至少有两次读取(左边元素除外),或者是只要第一个线程完全对齐后的内存,我开始阅读的地方无关紧要?另请注意,如果不是这种情况,那么在第一个之后我会对每一行的数组进行未对齐访问,因为我的数组的宽度是(32 * x)+2,因此不是32字节对齐的。然而,进一步的填充将解决每个新行的问题。
问题:我的理解是正确的,在上面的示例中,只有第一行允许同时访问,而且只允许数组中的左元素,因为这是唯一一个没有任何访问的元素偏移?
在warp中执行的线程:
当且仅当所有指令都相同时(根据link),warp中的线程才会并行执行。如果我确实有条件语句/分歧执行,那么该特定线程将由其自身执行,而不是与其他线程进行扭曲。
例如,如果我初始化数组,我可以执行类似
的操作1 int x = threadIdx.x;
2
3 array[x+1] = globalArray[blockIdx.x * blockDim.x + x]; // remember the border and therefore use +1
4
5 if (x == 0 || x == blockDim.x-1) // border
6 {
7 array[x] = DBL_MAX;
8 }
warp的大小是32并且并行执行直到第3行然后为所有其他线程停止,并且只有第一个和最后一个线程进一步执行以初始化边界,或者那些将与已经在该边界处的所有其他线程分开开头,因为有一个if语句,所有其他线程都不满足?
问题:如何将线程收集到单个warp中? warp中的每个线程都需要共享相同的指令。需要这个对整个功能有效吗?线程1(x = 0)不是这种情况,因为它也初始化边界,因此与其他边缘不同。根据我的理解,线程1在另一个warp中的单个warp,线程(2-33等)中执行,然后由于未对齐而不会在单个读取中访问内存,然后再次最终由于其他边界而在单个扭曲中的线程。这是对的吗?
我想知道最佳实践是什么,让每个行的内存完全对齐(在这种情况下,我将使用(32 * x-2)个线程运行每个块,以便带边框的数组为(32 * x- 2)+2每个新行的32的倍数)或者按照我上面演示的方式进行,每个块的线程为32的倍数,并且只与未对齐的内存一起使用。我知道这些问题并不总是直截了当的,往往取决于特定情况,但有时某些事情是不好的做法,不应成为习惯。
当我进行一些实验时,我并没有真正注意到执行时间的差异,但也许我的例子太简单了。我试图从视觉分析器中获取信息,但我还没有真正理解它给我的所有信息。然而,我得到一个警告,我的入住率是17%,我认为必须非常低,因此我做错了。我没有找到有关如何并行执行线程以及我的内存访问效率的信息。
- 编辑 -
添加并突出显示了2个问题,一个是关于内存访问的,另一个是关于如何将线程收集到单个warp中。
答案 0 :(得分:2)
现在,我的理解是因为我确实有一个不可避免的偏移量(+ 1,+ 2),每个warp和每个赋值至少有两次读取(左边元素除外),或者是只要第一个线程完全对齐后的内存,我开始阅读的地方无关紧要?
是的,如果您想要实现完美的合并,那么“从您开始阅读的位置”确实很重要。完美合并意味着给定warp和给定指令的读取活动都来自相同的128字节对齐的高速缓存行。
问题:我的理解是否正确,在上面的例子中,只有第一行允许同时访问,而且只允许数组中的左元素,因为这是唯一一个没有任何偏移的访问?
是。对于cc2.0及更高版本的设备,缓存可以缓解未对齐访问的一些缺点。
问题:如何将线程收集到单个warp中? warp中的每个线程都需要共享相同的指令。需要这个对整个功能有效吗?线程1(x = 0)不是这种情况,因为它也初始化边界,因此与其他边缘不同。根据我的理解,线程1在另一个warp中的单个warp,线程(2-33等)中执行,然后由于未对齐而不会在单个读取中访问内存,然后再次最终由于其他边界而在单个扭曲中的线程。这是对的吗?
将线程分组为warp总是follows the same rules,并且不会根据您编写的代码的具体情况而有所不同,但仅受启动配置的影响。当您编写并非所有线程都参与的代码(例如在if语句中)时,warp仍然以锁步方式继续进行,但是不参与的线程是空闲的。当你填写这样的边界时,很少能够完全对齐或合并读取,所以不要担心它。机器为您提供了灵活性。