OpenCL内核执行速度比单线程慢

时间:2013-03-29 19:32:58

标签: c optimization opencl gpgpu

所有,我写了一个非常简单的OpenCL内核,它使用简单的平均值将RGB图像转换为灰度。

一些背景知识:

  1. 图像存储在映射内存中,作为24位非填充内存块
  2. 输出数组存储在固定内存中(使用clEnqueueMapBuffer映射)并且是8 bpp
  3. 在设备上分配了两个缓冲区(clCreateBuffer),一个是专门读取的(在内核启动之前我们clWriteBuffer),另一个是专门写入的(我们clReadBuffer在内核完成后)
  4. 我在1280x960图像上运行它。该算法的串行版本平均为60ms,OpenCL内核平均为200ms!我做错了什么,但我不知道如何继续,优化什么。 (定时我的读/写没有内核调用,算法运行15ms)

    我附加了内核设置(大小和参数)以及内核


    编辑:所以我写了一个偶数dumber内核,里面没有全局内存访问,而且它只有150ms ......这仍然是非常慢的。我想也许我搞乱全局内存读取,它们必须是4字节对齐或什么?都能跟得上...

    编辑2:从我的内核中删除所有参数让我显着加快...我很困惑我认为因为我clEnqueueWriteBuffer内核应该不做内存从主机 - >设备和设备 - >主机....

    传输

    编辑3:想出来,但我仍然不明白为什么。如果有人能解释,我很乐意给他们正确答案。问题是按值传递自定义结构。看起来我需要为它们分配一个全局内存位置并传递它们的cl_mem s


    内核通话:

    //Copy input to device
    result = clEnqueueWriteBuffer(handles->queue, d_input_data, CL_TRUE, 0, h_input.widthStep*h_input.height, (void *)input->imageData, 0, 0, 0);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to write to input buffer on device!")) return 0;
    
    //Set kernel arguments
    result = clSetKernelArg(handles->current_kernel, 0, sizeof(OpenCLImage), (void *)&h_input);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to set input struct.")) return 0;
    result = clSetKernelArg(handles->current_kernel, 1, sizeof(cl_mem), (void *)&d_input_data);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to set input data.")) return 0;
    result = clSetKernelArg(handles->current_kernel, 2, sizeof(OpenCLImage), (void *)&h_output);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to set output struct.")) return 0;
    result = clSetKernelArg(handles->current_kernel, 3, sizeof(cl_mem), (void *)&d_output_data);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to set output data.")) return 0;
    
    //Determine run parameters
    global_work_size[0] = input->width;//(unsigned int)((input->width / (float)local_work_size[0]) + 0.5);
    global_work_size[1] = input->height;//(unsigned int)((input->height/ (float)local_work_size[1]) + 0.5);
    
    printf("Global Work Group Size: %d %d\n", global_work_size[0], global_work_size[1]);
    
    //Call kernel
    result = clEnqueueNDRangeKernel(handles->queue, handles->current_kernel, 2, 0, global_work_size, local_work_size, 0, 0, 0);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to run kernel!")) return 0;
    
    result = clFinish(handles->queue);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to finish!")) return 0;
    
    //Copy output
    result = clEnqueueReadBuffer(handles->queue, d_output_data, CL_TRUE, 0, h_output.widthStep*h_output.height, (void *)output->imageData, 0, 0, 0);
    if(check_result(result, "opencl_rgb_to_gray", "Failed to write to output buffer on device!")) return 0;
    

    内核:

    typedef struct OpenCLImage_t
    {
        int width;
        int widthStep;
        int height;
        int channels;
    } OpenCLImage;
    
    __kernel void opencl_rgb_kernel(OpenCLImage input, __global unsigned char*  input_data, OpenCLImage output, __global unsigned char * output_data)
    {
        int pixel_x = get_global_id(0);
        int pixel_y = get_global_id(1);
        unsigned char * cur_in_pixel, *cur_out_pixel;
        float avg = 0;
    
        cur_in_pixel = (unsigned char *)(input_data + pixel_y*input.widthStep + pixel_x * input.channels);
        cur_out_pixel = (unsigned char *)(output_data + pixel_y*output.widthStep + pixel_x * output.channels);
    
        avg += cur_in_pixel[0];
        avg += cur_in_pixel[1];
        avg+= cur_in_pixel[2];
        avg /=3.0f;
    
        if(avg > 255.0)
            avg = 255.0;
        else if(avg < 0)
            avg = 0;
    
        *cur_out_pixel = avg;
    }
    

2 个答案:

答案 0 :(得分:4)

将值复制到将要创建的所有线程的开销可能是时间的可能原因;对于全局存储器,在另一种情况下引用就足够了。唯一的SDK实现者将能够准确回答.. :))

答案 1 :(得分:0)

您可能想尝试像[64,1,1]这样的local_work_size,以便合并您的内存调用。 (注意64是1280的加法器。)

如前所述,您必须使用分析器才能获得更多信息。你在使用nvidia卡吗?然后下载CUDA 4(不是5),因为它包含一个openCL分析器。

你的表现必须远离最佳状态。更改本地工作大小,全局工作大小,尝试每个胎面处理两个或四个像素。你能改变像素的储存方式吗?然后打破树形数组的结构,以便更有效地合并memomry访问。

Tou可以通过GPU工作隐藏你的内存传输:使用你附近的探查器会更容易。