c++ - 为什么 OpenCL 工作组大小对 GPU 的性能影响巨大?
问题描述
我在 Qualcomm Adreno 630 GPU 上对一个简单的矩阵转置内核进行基准测试,并试图查看不同工作组大小的影响,但令人惊讶的是,我得到了一些我无法解释的有趣结果。这是我的内核代码:
__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
大多数场景的执行时间显着下降。我知道内存合并或缓存可以在这里发挥作用,但我不能完全解释这一点。
解决方案
当连续线程访问 128 字节对齐段内连续全局内存地址的数据时,就会发生内存合并。然后将内存访问合并为一个,显着降低整体延迟。
在 2D 范围内,合并仅在您的情况下沿get_global_id(1)
或j
方向发生。在该行output[i*height + j] = input[j*width + i];
中,input[j*width + i];
是未对齐(非合并)读取并且output[i*height + j]
是合并写入。合并内存访问通常比未对齐访问快得多,但合并/未对齐读取的性能损失可能与合并/未对齐写入大不相同。在大多数桌面 GPU 架构上,未对齐读取和合并写入的组合比其他方式更快,请参见下图。所以你的实现应该已经是更快的变体了。
由于只能沿j
索引进行合并访问,因此如果您有一个范围(x=256,y=1)
(i
沿-x
方向,j
沿y
-方向),则不会获得任何合并。对于(x=8,y=32)
,j
每个线程块以 32 8 次为一组合并,因此内存带宽相当饱和且性能良好。
如果您想要最大可能的性能,我建议您使用一维索引。这样您就可以完全控制合并和合并发生在整个线程块上。您的矩阵转置内核将如下所示:
#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];
}
您可以width
通过字符串连接在 C++ 运行时和 OpenCL 编译前烘焙到 OpenCL Ccode。
推荐阅读
- jdbc - 尝试路由 HTTPS 流量时,SSH 隧道无法与 JDBC 一起使用
- ios - 在 Swift 中按条件使用 dispatchqueue 的最佳方法
- android - (kotlin) editText.toString().toInt() 在 anroid studio 中不起作用
- android - 连接的线程返回空对象
- ruby-on-rails - Rails 5.2 服务器错误 - 完成新手
- php - 取决于从和到日期excel报告需要在php中下载
- python - 在 Python 中为条形图的多个条形设置不同的颜色?
- node.js - Jenkins - env:'node':没有这样的文件或目录
- javascript - 使用对象属性从对象数组中删除一个对象
- ruby-on-rails - 功能测试在生产中工作但不在开发中