我从CUDA开始,编写了两个用于实验的内核。 乳清都接受3个指向n * n(矩阵仿真)和n数组的指针。
__global__
void th_single_row_add(float* a, float* b, float* c, int n) {
int idx = blockDim.x * blockIdx.x * n + threadIdx.x * n;
for (int i = 0; i < n; i ++) {
if (idx + i >= n*n) return;
c[idx + i] = a[idx + i] + b[idx + i];
}
}
__global__
void th_single_col_add(float* a, float* b, float* c, int n) {
int idx = blockDim.x * blockIdx.x + threadIdx.x;
for (int i = 0; i < n; i ++) {
int idx2 = idx + i * n;
if (idx2 >= n*n) return;
c[idx2] = a[idx2] + b[idx2];
}
}
在th_single_row_add
中,每个线程求和n
个元素上的行;在th_single_col_add
中,每个线程求和列。
这是n = 1000
上的配置文件(1000万个元素)
986.29us th_single_row_add(float*, float*, float*, int)
372.96us th_single_col_add(float*, float*, float*, int)
如您所见,列的求和速度快三倍。
我以为,因为在column
变体中,循环中的所有索引都相距较远,所以应该变慢,我在哪里弄错了?
答案 0 :(得分:4)
CUDA中的线程不是单独起作用的,它们是grouped together in warps of 32 threads。这32个线程通常按步执行。发给一个线程的指令在同一时钟周期内同时发给所有32个线程。
例如,如果该指令是读取存储器的指令,则可能需要/请求多达32次独立读取。满足这些读取操作所需的地址的确切模式由您编写的代码确定。如果这些地址在内存中全部“相邻”,那将是一个有效的读取。如果这些地址以某种方式“分散”在内存中,那将是低效的读取,并且速度会变慢。
刚刚描述的这个基本概念在CUDA中称为“合并访问”。您的列求和情况允许在一个warp中合并访问,因为warp中每个线程生成的地址在相邻的列中,并且位置在内存中是相邻的。您的行总和情况打破了这一点。经线中每个线程生成的地址不是相邻的(它们是“列”,彼此之间按数组的宽度分开),因此不“合并”。
性能上的差异是由于内存访问效率上的差异。
您可以通过研究CUDA优化的介绍性方法来研究CUDA中的合并行为,例如here,尤其是幻灯片44-54。