我正在OpenCL中进行一些本地/全局内存优化;在看了两年前的this question之后,我认为我做错了,因为本地内存IO似乎比它应该慢得多。我的GPU是Intel HD 6000。
这是我的测试设置,内核源代码:
__kernel void vecAdd(__global float* results, const unsigned int n, __local float* loc)
{
int id = get_global_id(0);
if(id < n) {
float rtemp = 0;
loc[23] = 34;
for(int i = 0; i < 1024; i ++) {
rtemp += loc[(i * 445) % 1024];
}
results[id] = rtemp;
}
}
所有内核都采用本地float数组 loc 并将随机值添加到全局输出向量中。片段“(i * 445)%1024”用于确保随机访问本地存储器;性能比没有随机化的最终提到的数字好一点(加速~30%)。
我将内核排队等待16777216 / 16M次迭代,工作组大小为256,本地缓冲区为1024个浮点数,全部为零,除了l [23]。
总的来说,这使得总共16M * 1 = 16M写入和16M * 1024 = 16G读取到本地存储器。
还有大约16M * 1024 * 2的浮点运算,可能更多取决于模数的计算方式,但HD 6000的浮点性能大约为768 GFLOPS,这应该不是瓶颈。
16G读取浮点值导致64G内存被读取;执行内核需要453945μs才能完成,估计本地内存带宽 151 GB / s 。
参考问题中提到的数字表明现代显卡(从2014年开始)可能具有比我在机器上测量的更高的内存带宽;文章中引用的数字(可能是一个随机的比较例子)为3-4 TB / s;虽然我的卡是一张集成卡而不是专用卡,但考虑到它在2015年发布,这似乎仍然是一个缓慢的数字。
为了让事情变得更加混乱,我在一些专用的中档GPU上的性能越来越差:AMD R9 m370x和Nvidia GT 750m都需要700-800 ms。这些卡比英特尔的HD 6000略长,因此可能与它有关。
是否有任何可能的方法可以从本地内存中挤出更多性能,或者我是否尽可能高效地利用本地内存?
答案 0 :(得分:3)
Intel HD 6000有两个切片,每个切片有三个子接口,每个子接口分别连接到共享本地存储器(参见此处http://www.notebookcheck.net/Intel-HD-Graphics-6000.125588.0.html图表),每个周期带宽为64字节,所以假设1 GHz时钟,则得到6 * 64 * 1 GHz = 384 GB / s的本地内存峰值BW。如果您正在击中16个本地存储器中的每一个(本地存储器是高度存储的,那么您可以独立地从每个存储区获取每个周期4个字节)。你可以通过loc [id]访问或类似的东西获得那种模式。下载适用于OpenCL的英特尔SDK https://software.intel.com/en-us/intel-opencl - 它为您提供了汇编视图以及其他内容:您的代码将被编译为SIMD32,但您的代码生成的程序集非常糟糕,因为您不断地从每个SIMD通道冲击相同的位置,所以你很幸运,你的速度高达151 GB / s。
答案 1 :(得分:1)
在答案结束时,答案位于 edit2 部分。
如果专用的gpu时序不好,你可以尝试流水线读取+计算+写操作,如
从左到右,它在第二步开始重叠操作,因此隐藏计算延迟,然后第三步也隐藏写延迟。这是将可分离的工作分为4个部分的示例。也许更多的部件会给出较慢的结果,应该根据设备进行基准测试内核执行只是一个&#34;添加&#34;所以它总是被隐藏但更重的可能不是。如果该图形卡可以同时进行读写,则可以减少I / O延迟。图片还显示了空闲(垂直空)时间轴,因为冗余同步使其比打包但更快的版本更具可读性。
您的igpu 151 GB / s带宽可能是cpu-cache。它没有可寻址的寄存器空间,所以即使使用__private寄存器也可以从缓存中获取它。此缓存还有每个cpu或gpu不同的行宽。
loc [23] = 34;
有多个线程的竞争条件并被序列化。
有可能
for(int i = 0; i&lt; 1024; i ++){ rtemp + = loc [(i * 445)%1024]; }
自动展开并对指令缓存和缓存/内存施加压力。您可以尝试不同级别的展开。
您确定使用了igpu的每个执行单元8个核心吗?也许每个EU仅使用1个核心,这可能不足以完全强调缓存/内存(例如通过使用所有第一个核心但没有其他内容的缓存线冲突)?尝试使用float8版本而不是浮动版本。最新的intel cpus每秒超过TB。
很少接近GFLOPS限制。大约50%使用优化代码,%75使用不可重复代码,%90使用无意义代码。
编辑下面的代码是在AMD-R7-240卡上以900MHz(不超过30 GB / s内存和600 GFlops)运行,用于16M元素的结果。
__kernel void vecAdd(__global float* results )
{
int id = get_global_id(0);
__local float loc[1024]; // some devices may slow with this
if(id < (4096*4096)) {
float rtemp = 0;
loc[23] = 34;
for(int i = 0; i < 1024; i ++) {
rtemp += loc[(i * 445) % 1024];
}
results[id] = rtemp;
}
}
花了
Edit2:优化高速缓存行利用率,计算简化并减少着色器核心中的气泡:
__kernel void vecAdd(__global float* results )
{
int id = get_global_id(0);
int idL = get_local_id(0);
__local float loc[1024];
float rtemp = 0;
if(id < (4096*4096)) {
loc[23] = 34;
}
barrier (CLK_LOCAL_MEM_FENCE);
if(id < (4096*4096)) {
for(int i = 0; i < 1024; i ++) {
rtemp += loc[(i * 445+ idL) & 1023];
}
results[id] = rtemp;
}
}
loc [(i * 445)%1024];
对于所有线程都是相同的,所有线程都是随机的,但在每一步都改变为相同的值,通过相同的缓存线访问。向所有线程添加局部变量但最终具有相同的求和,使用更多行。
%1024
使用
进行优化&安培; 1023
最后,阻止在loc [23] = 34;
之后消除SIMD中的任何指令气泡编辑3:添加一些循环展开并将本地工作组大小从64增加到256(编辑和编辑2为64)
__kernel void vecAdd(__global float* results )
{
int id = get_global_id(0);
int idL = get_local_id(0);
__local float loc[1024];
float rtemp = 0;
float rtemp2 = 0;
float rtemp3 = 0;
float rtemp4 = 0;
if(id < (4096*4096)) {
loc[23] = 34;
}
barrier (CLK_LOCAL_MEM_FENCE);
if(id < (4096*4096)) {
int higherLimitOfI=1024*445+idL;
int lowerLimitOfI=idL;
int stepSize=445*4;
for(int i = lowerLimitOfI; i < higherLimitOfI; i+=stepSize) {
rtemp += loc[i & 1023];
rtemp2 += loc[(i+445) & 1023];
rtemp3 += loc[(i+445*2) & 1023];
rtemp4 += loc[(i+445*3) & 1023];
}
results[id] = rtemp+rtemp2+rtemp3+rtemp4;
}
}
结果[id] = ...
__全局数组访问是该算法对此设备的瓶颈。
对于HD 400,230 ms而不是1410 ms !!!! (这应该是缓存/本地带宽)