OpenCL

时间:2016-09-06 21:24:13

标签: c++ c memory-management opencl gpgpu

我正在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略长,因此可能与它有关。

是否有任何可能的方法可以从本地内存中挤出更多性能,或者我是否尽可能高效地利用本地内存?

2 个答案:

答案 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时序不好,你可以尝试流水线读取+计算+写操作,如

enter image description here

从左到右,它在第二步开始重叠操作,因此隐藏计算延迟,然后第三步也隐藏写延迟。这是将可分离的工作分为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;
           }
        }

花了

  • 写入+计算+读取
  • 575毫秒(无管道)
  • 530毫秒(2部分流水线)写+计算+读
  • 510毫秒(8部分流水线)写+计算+读
  • 计算455毫秒(140 GB / s本地内存带宽)

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;
           }
        }
  • 325毫秒(16部分流水线)写+计算+读
  • 计算270毫秒(235 GB / s本地内存带宽)
  

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;
           }
        }
  • 290毫秒(8部分流水线)写入+计算+读取而没有冗余同步(在其他基准测试中忘记了这一点)
  • 在pci-e 2.0 8x而不是4x
  • 上278毫秒
  • 4个队列上的249毫秒(rcw + rcw + rcw + rcw)没有事件而不是事件流的3个队列(r + c + w)。 (每个队列32个部分,因此总共128x rcw部分)
  • 243毫秒来计算+(map / unmap而不是read / write)
  • 计算240毫秒(264 GB / s本地内存带宽)
  • 1410 ms Intel(R)HD Graphics 400 @ 600 MHz(45 GB / s)
    • 警告:它是__global数组访问
      

    结果[id] = ...

         

    __全局数组访问是该算法对此设备的瓶颈。

         对于HD 400,

    230 ms而不是1410 ms !!!! (这应该是缓存/本地带宽)

     
  • 12个计算单元,每个计算单元有8个核心=&gt; 96个核心45 GB / s表示1个核心0.5 GB / s @ 600 MHz或**每个核心每个核心近1个字节**
  • < li>你的igpu每3个周期可以读取每个核心1B,但总共有384个核心=&gt; ** 192 GB / s(接近极限)**
  • 看看这张图片,它每片写入64B,这意味着每个周期每192个核心64个字节或每3个周期192个核心读取192个字节:

- 根据分析器,VGPR使用将内核占用率限制为%60。 enter image description here