【问题标题】:Optimizing opencl kernel优化opencl内核
【发布时间】:2016-01-17 23:58:21
【问题描述】:

我正在尝试优化这个内核。该内核的 CPU 版本比 GPU 版本快 4 倍。我希望 GPU 版本会更快。 可能是我们有很多内存访问,这就是我们性能低的原因。我使用的是 Intel HD 2500 和 OpenCL 1.2。

GPU 内核是:

__kernel void mykernel(__global unsigned char *inp1,    
                        __global  unsigned char *inp2,      
                        __global  unsigned char *inp3,          
                        __global  unsigned char *inp4,          
                        __global  unsigned char *outp1,     
                        __global  unsigned char *outp2,     
                        __global  unsigned char *outp3,     
                        __global  unsigned char *outp4,     
                        __global  unsigned char *lut,           
                        uint size
                        )               
{
  unsigned char x1, x2, x3, x4;
  unsigned char y1, y2, y3, y4;
   const int x     = get_global_id(0);                      
   const int y     = get_global_id(1);                          
   const int width = get_global_size(0);                        
   const uint id = y * width + x;                               
    x1 = inp1[id];
    x2 = inp2[id];
    x3 = inp3[id];
    x4 = inp4[id];
    y1 = (x1 & 0xff) | (x2>>2 & 0xaa) | (x3>>4 & 0x0d) | (x4>>6 & 0x02);
    y2 = (x1<<2 & 0xff) | (x2 & 0xaa) | (x3>>2 & 0x0d) | (x4>>4 & 0x02);
    y3 = (x1<<4 & 0xff) | (x2<<2 & 0xaa) | (x3 & 0x0d) | (x4>>2 & 0x02);
    y4 = (x1<<6 & 0xff) | (x2<<4 & 0xaa) | (x3<<2 & 0x0d) | (x4 & 0x02);
    // lookup table
    y1 = lut[y1];
    y2 = lut[y2];
    y3 = lut[y3];
    y4 = lut[y4];
    outp1[id] =    (y1 & 0xc0)
                 | ((y2 & 0xc0) >> 2)
                 | ((y3 & 0xc0) >> 4)
                 | ((y4 & 0xc0) >> 6);        
    outp2[id] =   ((y1 & 0x30) << 2)
                 |  (y2 & 0x30)
                 | ((y3 & 0x30) >> 2)
                 | ((y4 & 0x30) >> 4);             
    outp3[id] =   ((y1 & 0x0c) << 4)
                 | ((y2 & 0x0c) << 2)
                 |  (y3 & 0x0c)
                 | ((y4 & 0x0c) >> 2);            
    outp4[id] =   ((y1 & 0x03) << 6)
                 | ((y2 & 0x03) << 4)
                 | ((y3 & 0x03) << 2)
                 |  (y4 & 0x03);
}

我用:

   size_t localWorkSize[1], globalWorkSize[1];
   localWorkSize[0] = 1;
   globalWorkSize[0] = X*Y; // X,Y define a data space of 15 - 20 MB

LocalWorkSize 可以在 1 - 256 之间变化。

for LocalWorkSize = 1 I have 
CPU = 0.067Sec
GPU = 0.20Sec
for LocalWorkSize = 256 I have 
CPU = 0.067Sec
GPU = 0.34Sec

这真的很奇怪。你能给我一些想法,为什么我会得到这些奇怪的数字吗?您对我如何优化这个内核有什么建议吗?

我的主要看起来像这样:

int main(int argc, char** argv)
{
int err,err1,j,i;                     // error code returned from api calls and other
   clock_t start, end;                 // measuring performance variables
   cl_device_id device_id;             // compute device id 
   cl_context context;                 // compute context
   cl_command_queue commands;          // compute command queue
   cl_program program_ms_naive;       // compute program
   cl_kernel kernel_ms_naive;         // compute kernel
   // ... dynamically allocate arrays
   // ... initialize arrays
 cl_uint dev_cnt = 0;
   clGetPlatformIDs(0, 0, &dev_cnt);

   cl_platform_id platform_ids[100];
   clGetPlatformIDs(dev_cnt, platform_ids, NULL);
   // Connect to a compute device
   err = clGetDeviceIDs(platform_ids[0], CL_DEVICE_TYPE_GPU, 1, &device_id, NULL);
    // Create a compute context 
   context = clCreateContext(0, 1, &device_id, NULL, NULL, &err);
   // Create a command queue
   commands = clCreateCommandQueue(context, device_id, 0, &err);
   // Create the compute programs from the source file
   program_ms_naive = clCreateProgramWithSource(context, 1, (const char **) &kernelSource_ms, NULL, &err);
    // Build the programs executable
   err = clBuildProgram(program_ms_naive, 0, NULL, NULL, NULL, NULL);
    // Create the compute kernel in the program we wish to run
   kernel_ms_naive = clCreateKernel(program_ms_naive, "ms_naive", &err);

   d_A1 = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size_cpy/4, h_A1, &err);
   d_A2 = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size_cpy/4, h_A2, &err);
   d_A3 = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size_cpy/4, h_A3, &err);
   d_A4 = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size_cpy/4, h_A4, &err);
   d_lut = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, 256, h_ltable, &err);
   d_B1 = clCreateBuffer(context, CL_MEM_WRITE_ONLY, mem_size_cpy/4, NULL, &err);
   d_B2 = clCreateBuffer(context, CL_MEM_WRITE_ONLY, mem_size_cpy/4, NULL, &err);
   d_B3 = clCreateBuffer(context, CL_MEM_WRITE_ONLY, mem_size_cpy/4, NULL, &err);
   d_B4 = clCreateBuffer(context, CL_MEM_WRITE_ONLY, mem_size_cpy/4, NULL, &err);

   int size = YCOLUMNS*XROWS/4; 
   int size_b = size * 4;
   err = clSetKernelArg(kernel_ms_naive,  0, sizeof(cl_mem), (void *)&(d_A1));
   err |= clSetKernelArg(kernel_ms_naive, 1, sizeof(cl_mem), (void *)&(d_A2));
   err |= clSetKernelArg(kernel_ms_naive, 2, sizeof(cl_mem), (void *)&(d_A3));
   err |= clSetKernelArg(kernel_ms_naive, 3, sizeof(cl_mem), (void *)&(d_A4));
   err |= clSetKernelArg(kernel_ms_naive, 4, sizeof(cl_mem), (void *)&d_B1);
   err |= clSetKernelArg(kernel_ms_naive, 5, sizeof(cl_mem), (void *)&(d_B2));
   err |= clSetKernelArg(kernel_ms_naive, 6, sizeof(cl_mem), (void *)&(d_B3));
   err |= clSetKernelArg(kernel_ms_naive, 7, sizeof(cl_mem), (void *)&(d_B4));
   err |= clSetKernelArg(kernel_ms_naive, 8, sizeof(cl_mem), (void *)&d_lut); //__global
   err |= clSetKernelArg(kernel_ms_naive, 9, sizeof(cl_uint), (void *)&size_b);
   size_t localWorkSize[1], globalWorkSize[1];
   localWorkSize[0] = 256;
   globalWorkSize[0] = XROWS*YCOLUMNS;
   start = clock(); 
   for (i=0;i< EXECUTION_TIMES;i++)
   {
       err1 = clEnqueueNDRangeKernel(commands, kernel_ms_naive, 1, NULL, globalWorkSize, localWorkSize, 0, NULL, NULL);
       err = clFinish(commands);
    }
   end = clock();

return 0;
}

【问题讨论】:

  • 本地工作大小应该是 256ish 而不是 1。1=最低的硬件占用和最低的性能。也许它需要是 8 的最小值或 8 的倍数。
  • 如果将CL_MEM_COPY_HOST_PTR 更改为CL_MEM_USE_HOST_PTR 会怎样?也可能是你的内核做的太少了,还在lut中随机访问全局内存。尝试在内核中添加更多工作,并在本地内存中缓存lut,以便更快地访问。
  • 我是否必须将 LUT 作为全局传递,然后在内核内部将其复制到本地数组中? 是的,这就是你需要做的。 如果是,我怎样才能只做一次?我不希望每个线程在每次执行时都执行此初始化是的,您需要在每次执行内核时都执行此操作(您不能通过内核参数传递本地数据)因此您可能需要添加更多每个内核工作。
  • @Nick 当然你总会有一些操作。但是全局内存读取非常昂贵,比操作高几个数量级。因此,一些移位和 OR 操作比内存 IO(2 次读取 + 1 次写入)要快得多,导致 IO 出现明显的瓶颈。 IO 问题对 CL 不利。本地 LUT 可以部分解决问题,但 IO 仍然是您的问题。

标签: optimization kernel opencl gpu


【解决方案1】:

常量内存用于向所有工作项广播少量值,其作用类似于常量私有寄存器,因此访问速度非常快。普通 GPU 设备最多可以支持 16kb 的常量内存。应该足以容纳 LUT。

您可以尝试使用常量内存,作为全局访问瓶颈的简单解决方案:

__kernel void mykernel(const __global unsigned char *inp1,    
                        const __global  unsigned char *inp2,      
                        const __global  unsigned char *inp3,          
                        const __global  unsigned char *inp4,          
                        __global  unsigned char *outp1,     
                        __global  unsigned char *outp2,     
                        __global  unsigned char *outp3,     
                        __global  unsigned char *outp4,     
                        __constant unsigned char *lut,           
                        uint size
                        )               
{
  ...
}

但正确的解决方案是重塑您的代码:

  • 使用 char4 的向量而不是 4 个不同的缓冲区(因为 打破结合)[它可以给你带来高达 x4 的巨大提升]
  • 对向量进行操作 [轻微提升]
  • 为 LUT 使用本地/常量内存 [它可以减少 1 次 LUT 的非合并读取,可能是 2x-3x]

由于较大的 IO 限制,仍然难以击败 CPU 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多