[英]Why OpenCL work group size has huge performance impact on GPU?
我正在对 Qualcomm Adreno 630 GPU 上的简单矩阵转置 kernel 进行基准测试,我试图查看不同工作组规模的影响,但令人惊讶的是,我得到了一些我无法解释的有趣结果。 这是我的 kernel 代码:
__kernel void transpose(__global float *input, __global float *output, const int width, const int height)
int i = get_global_id(0);
int j = get_global_id(1);
output[i*height + j] = input[j*width + i];
}
并且宽度和高度都是6400,实验结果是(执行时间是END和START事件的差):
work group size execution time
x y
4 64 24ms
64 4 169ms
256 1 654ms
1 256 34ms
8 32 27ms
1 1024 375ms
1024 1 657ms
32 32 26ms
在此之后,我做了另一个实验,将宽度和高度从 6400 更改为 6401(以及 NDRangeKernel 调用中的全局工作大小),结果更加有趣:
work group size execution time
x y
4 64 28ms
64 4 105ms
256 1 359ms
1 256 31ms
8 32 32ms
1 1024 99ms
1024 1 358ms
32 32 32ms
大多数场景的执行时间显着下降。 我知道 memory 合并或缓存可以在这里发挥作用,但我不能完全解释这一点。
当连续线程访问 128 字节对齐段内连续全局 memory 地址的数据时,会发生 Memory 合并。 然后 memory 访问合并为一个,显着降低了整体延迟。
在 2D 范围内,合并仅发生在get_global_id(1)
或您的情况下的j
方向。 在行output[i*height + j] = input[j*width + i];
, input[j*width + i];
是未对齐(未合并)的读取,并且output[i*height + j]
是合并的写入。 合并 memory 访问通常比未对齐访问快得多,但合并/未对齐读取的性能损失可能与合并/未对齐写入有很大不同。 在大多数桌面 GPU 架构上,未对齐读取和合并写入的组合比相反的方式更快,请参见下图。 所以你的实现应该已经是更快的变体了。
由于只能沿j
索引进行合并访问,因此如果您的范围为(x=256,y=1)
( i
沿x
方向, j
沿y
方向),则不会获得任何合并。 对于(x=8,y=32)
, j
以每个线程块 32 8 次为一组合并,因此 memory 带宽相当饱和并且性能良好。
如果您想要最大可能的性能,我建议您使用一维索引的 go。 这样您就可以完全控制合并和合并发生在整个线程块上。 您的矩阵转置 kernel 然后看起来像这样:
#define width 6400
__kernel void transpose(__global float *input, __global float *output) {
const int n = get_global_id(0);
int i = n/width;
int j = n%width;
output[i*height + j] = input[j*width + i];
}
您可以在 C++ 运行时和 OpenCL 编译时间之前通过字符串连接将width
烘焙到 OpenCL Ccode 中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.