在opencl中为GPU优化内核代码

VedhaR

到目前为止,在内核执行时间方面,我的GPU慢于我的CPU。我以为也许是因为我只测试了一个小样本,所以由于启动开销较小,CPU最终完成得更快。但是,当我用几乎是样本大小10倍的数据测试内核时,CPU仍然完成得更快,而GPU却落后了将近400毫秒。

使用2.39MB文件运行时CPU:43.511ms GPU:65.219ms

使用32.9MB文件的运行时CPU:289.541ms GPU:605.400ms

我尝试使用本地内存,尽管我100%确信使用了错误的内存,但遇到了两个问题。内核完成1000-3000ms之间的任何时间(取决于我为localWorkSize设置的大小),或者遇到状态代码-5,即CL_OUT_OF_RESOURCES。

这是一个SO成员同伴帮助我的内核。

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=Array[i+globalId-64]*coefficients[64-i];    

    }

    sum += tmp;

}
Output[globalId]=sum;
}

这是我尝试使用本地内存的尝试。第一部分将是主机代码的一部分,随后的部分是内核。

//Set the size of localMem
status |= clSetKernelArg(
    kernel,
    2,
    1024, //I had num_items*(float) but it gave me a -5. Num items is the amount of elements in my array (around 1.2 million elements)
    null);
printf("Kernel Arg output status: %i \n", status);

//set a localWorkSize
localWorkSize[0] = 64;

//execute the kernel with localWorkSize included
status = clEnqueueNDRangeKernel(
    cmdQueue,
    kernel,
    1,
    NULL,
    globalWorkSize,
    localWorkSize,
    0,
    NULL,
    &someEvent);


 //Here is what I did to the kernel*************************************** 
__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output, __local float *localMem) {

int globalId = get_global_id(0);
int localId = get_local_id(0);  

localMem[localId] = globalId[globalId];

float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=localMem[i+localId-64]*coefficients[64-i];  

    }

    sum += tmp;

}
Output[globalId]=sum;
}

尝试设置局部变量时使用的参考链接:如何在OpenCL中使用局部内存?

用于查找kernelWorkGroupSize的链接(这就是为什么在kernelArg中设置1024的原因):CL_OUT_OF_RESOURCES包含200万个具有1GB VRAM的浮点数?

我已经看到其他人也遇到类似的问题,其中GPU的速度比CPU慢,但是对于许多人来说,他们使用的是clEnqueueKernel而不是clEnqueueNDRangeKernel。

如果您需要有关此内核的更多信息,这是我之前的问题:在内核OpenCL中实现FIFO的最佳方法

还找到了一些针对GPU的优化技巧。https://developer.amd.com/wordpress/media/2012/10/Optimizations-ImageConvolution1.pdf

编辑的代码;错误仍然存​​在

__kernel void lowpass2(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
float tmp=0.0f;
for (int i=64-globalId; i< 65; i++)
{

tmp = 0.0f;
tmp=Array[i]*coefficients[i];    
sum += tmp;

}
Output[globalId]=sum;
}
侯赛因·图格鲁·布基基西克

为2400万个元素数组运行以下内核

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) {

int globalId = get_global_id(0); 
float sum=0.0f;
for (int i=0; i< 65; i++)
{
    float tmp=0;
    if (globalId+i > 63)
    {
        tmp=Array[i+globalId-64]*coefficients[64-i];    

    }

    sum += tmp;

}
Output[globalId]=sum;
}

25个计算单元设备池在200毫秒内完成,而8核CPU则在500毫秒内完成。

您有高端cpu和低端gpu,或者gpu驱动程序被卡住了,或者gpu的pci-e接口卡在pci-e 1.1 @ 4x带宽上,因此主机和设备之间的阵列副本受到限制。

另一方面,此优化版本:

__kernel void lowpass(__global __read_only float *Array,__constant  float *coefficients, __global __write_only float *Output) {

        int globalId = get_global_id(0); 
        float sum=0.0f;
        int min_i= max(64,globalId)-64;
        int max_i= min_i+65;
        for (int i=min_i; i< max_i; i++)
        {
            sum +=Array[i]*coefficients[globalId-i];    
        }
        Output[globalId]=sum;
}

对于cpu(8个计算单元)的时间少于150毫秒,对于gpu(25个计算单元)的时间少于80毫秒。每个项目的工作量只有65次。使用__constant和__read_only和__write_only参数说明符可以很容易地加速这种低数量的操作,并减少一些整数工作。

对于数组和输出,使用float4而不是float类型可以使cpu和gpu的速度提高%80,因为它们是SIMD类型和矢量计算单元。

该内核的瓶颈是:

  • 每个线程只有65个乘法和65个求和。
  • 但是数据仍然通过pci-express接口传输,速度很慢。
  • 每个浮点操作的1个条件检查(i <max_i)也很高,需要循环展开。
  • 一切都是标量的,尽管您的cpu和gpu是基于矢量的。

一般来说:

  • 第一次运行内核会及时触发opencl的编译器优化,速度很慢。准确运行至少5-10次。
  • __constant空间只有10-100 kB,但它比__global更快,对于AMD的hd5000系列来说非常有用。
  • 内核开销为100微秒,而65次缓存操作比内核开销少,并且被内核开销时间(甚至更糟糕的是,pci-e延迟)所掩盖。
  • 工作项目太少会使占用率降低,变慢。

还:

  • 由于分支预测,总缓存带宽,指令等待时间和无等待时间延迟,4核Xeon @ 3 GHz比16(vliw5的1/4)* 2(计算单元)= 32 gpu @ 600 MHz的核要快得多。
  • HD5000系列AMD卡是旧版的,与gimped相同。
  • HD5450具有166 GB / s的恒定内存带宽
  • 它也只有83 GB / s的LDS(本地内存)带宽
  • 它还具有83 GB / s的L1和L2高速缓存带宽,因此除非计划升级计算机,否则就让它在__global驱动程序优化而不是LDS上起作用。 83 + 83 = 166 GB / s带宽。你可以试试。就银行冲突而言,也许二比二比交替更好。

  • 使用系数作为__constant(166 GB / s)和数组作为__global应该给您166 + 83 = 249 GB / s的组合带宽。

  • 每个系数元素每个线程仅使用一次,因此我不建议使用专用寄存器(499 GB / s)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章