在opencl中为GPU

时间:2016-05-30 15:07:17

标签: c opencl

截至目前,在内核执行时,我的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;
}

尝试设置局部变量时使用的参考链接: How do I use local memory in OpenCL?

用于查找kernelWorkGroupSize的链接(这就是为什么我在kernelArg中设置了1024): CL_OUT_OF_RESOURCES for 2 millions floats with 1GB VRAM?

我见过其他人有类似的问题,其中GPU比CPU慢,但对于其中许多人来说,他们使用的是clEnqueueKernel而不是clEnqueueNDRangeKernel。

如果您需要有关此内核的更多信息,请继续我之前的问题: Best approach to FIFO implementation in a kernel OpenCL

为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;
}

2 个答案:

答案 0 :(得分:5)

为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驱动器已被gimped或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参数说明符以及一些整数工作减少可以非常容易地加速这种少量操作。

对于cpu和gpu,使用float4而不是float类型应该会增加cpu和gpu的速度%80,因为它们是SIMD类型和矢量计算单元。

这个内核的瓶颈是:

  • 每个帖子只有65次乘法和65次求和。
  • 但数据仍然通过pci-express界面传输,慢。
  • 每次浮动操作的1个条件检查(i&lt; max_i)也很高,需要循环展开。
  • 尽管你的cpu和gpu是基于矢量的,但一切都是标量。

一般而言:

  • 第一次运行内核会及时触发opencl编译器优化,缓慢。准确时间至少运行5-10次。
  • __恒定空间仅为10 - 100 kB但速度比__global快,对amd的hd5000系列也有好处。
  • 内核开销为100微秒,而65个缓存操作小于此值,并且被内核开销时间所掩盖(更糟糕的是,由于pci-e延迟)。
  • 工作项目太少会使占用率降低,变慢。

此外:

  • 4核Xeon @ 3 GHz比16(1/4 vliw5)快2 *(计算单位)= 32个gpu @ 600 MHz核心,因为分支预测,总缓存带宽,指令延迟和没有 - pcie延迟。
  • HD5000系列amd卡是传统的,与gimped相同。
  • HD5450具有166 GB / s的恒定内存带宽
  • 其中也只有83 GB / s的LDS(本地内存)带宽
  • 其中还有83 GB / s的L1和L2缓存带宽,所以只需让它在__global驱动程序优化而不是LDS上工作,除非你打算升级你的计算机。(对于Array ofcourse)也许,来自LDS的奇怪元素,来自__global的偶数元素可以具有83 + 83 = 166 GB / s的带宽。你可以试试。在银行冲突方面,可能两个比两个更好。

  • 将系数用作__constant(166 GB / s),将Array用作__global,可以为您提供166 + 83 = 249 GB / s的组合带宽。

  • 每个系数元素每个线程只使用一次,所以我不打算使用私有寄存器(499 GB / s)

答案 1 :(得分:3)

在介绍本地内存之前,让我们先将if语句移出循环:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{
int globalId = get_global_id(0); 
float sum=0.0f;
int start = 0;
if(globalId < 64)
    start = 64-globalId;
for (int i=start; i< 65; i++)
    sum += Array[i+globalId-64] * coefficients[64-i];    
Output[globalId]=sum;
}

然后可以像这样实现本地内存的引入:

__kernel void lowpass(__global float *Array, __global float *coefficients, __global float *Output) 
{
    int globalId = get_global_id(0);
    int local_id = get_local_id(0);

    __local float local_coefficients[65];
    __local float local_array[2*65];

    local_coefficient[local_id] = coefficients[local_id];
    if(local_id == 0)
        local_coefficient[64] = coefficients[64];
    for (int i=0; i< 2*65; i+=get_local_size(0))
    {
        if(i+local_id < 2*65)
            local_array[i+local_id] = Array[i+global_id];
    }
    barrier(CLK_LOCAL_MEM_FENCE);

    float sum=0.0f;
    int start = 0;
    if(globalId < 64)
        start = 64-globalId;
    for (int i=start; i< 65; i++)
        sum += local_array[i+local_id] * local_coefficient[64-i];    
    Output[globalId]=sum;
}

P.S。那里可能会出现一些错误,例如全局到本地索引重新计算等等(我现在要去睡觉了):)尽管如此,上面的实现应该让你正确指导如何开始使用本地内存。