如何加速计算Buddhabrot集的OpenCL内核随机写入操作?
我编写了一个用于计算Buddhabrot集的OpenCL内核,代码如下:
// ... initializing things while (true) { Z2 = Z * Z; Z = (float2)(Z2.x - Z2.y + Point.x, 2 * Z.x * Z.y + Point.y); X = ((Z.x - MinView.x) * iScale.x); Y = ((Z.y - MinView.y) * iScale.y); if (X >= 0 && X < Res.x && Y >= 0 && Y < Res.y) { Result[Offset + Y * Res.x + X]++; // <--- 注释此行可获得10倍性能提升 count++; } if (dot(Z, Z) > 4) { PointsOutPos = atomic_add(&atomicCounter[2], 1); if (PointsOutPos >= PointsOutSize) return; Point = convert_float2(PointsOut[PointsOutPos]); // Load next point to minimize thread divergence Z = 0; i = 0; } i++; }
注释掉Result[Offset + Y * Res.x + X]++这一行后,内核性能提升约10倍,可见随机写入是巨大性能瓶颈。这些写入不仅位置随机,且发生的迭代时机也各不相同(不过多数迭代都会执行)。
请问有什么方法可以加速该操作?我认为使用本地数组作为缓冲区无法解决问题,因为这只是改变写入时机,若批量写入可能仍会耗尽内存带宽。我的思路是否有误?是否可以在内存写入时让其他线程继续计算?
优化方案
本地内存缓冲+批量写入
你的思路存在误区,本地内存的核心价值不是单纯延迟写入,而是将大量随机全局原子操作转化为高效的本地原子操作,再合并为连续全局写入。每个工作组可以分配一块本地内存作为计数缓冲区,迭代时先把坐标对应的计数更新到本地内存(本地原子操作延迟极低,几乎不占性能),当本地缓冲区达到一定阈值、或者当前点迭代结束时,再同步工作组,把本地缓冲区的统计数据批量写入全局内存。这种方式能大幅减少全局内存的随机访问次数,同时连续写入的带宽利用率比随机写入高得多,不会轻易耗尽带宽。按图像分块处理,集中访问
不要让所有线程无差别处理整个图像的随机点,而是将图像划分为若干与工作组大小匹配的tile,每个工作组只负责统计自己tile范围内的迭代点。这样线程的写入目标会高度集中,全局内存的缓存命中率会显著提升,即使直接写入全局内存,性能也会比完全随机访问好很多。超出当前tile的点可以暂时存在本地缓冲区,后续统一处理。异步内存操作(设备支持时)
如果你的OpenCL设备支持2.0及以上版本,可以使用异步内存拷贝API,把批量写入全局内存的操作放到异步队列中,让线程在等待写入完成的同时,直接开始下一个点的迭代计算,实现计算与内存操作的重叠,充分利用硬件资源。优化全局原子操作
若必须直接写入全局内存,可尝试使用atomic_add的更高效重载版本,同时给Result数组添加restrict限定符(比如__global restrict int* Result),帮助编译器生成更优的内存访问代码;也可以检查设备是否支持硬件加速的原子指令,针对性调整代码。
内容的提问来源于stack exchange,提问作者Paul Aner

