【问题标题】:C vs OpenCL, how to compare results of time measurement?C vs OpenCL,如何比较时间测量的结果?
【发布时间】:2012-04-26 16:31:01
【问题描述】:

所以,在另一篇文章中,我对 C 时间测量提出了质疑。现在,我想知道如何比较 C“函数”与 OpenCL“函数”的结果

这是宿主OpenCL和C的代码

#define PROGRAM_FILE "sum.cl"
#define KERNEL_FUNC "float_sum"
#define ARRAY_SIZE 1000000


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <CL/cl.h>

int main()
{
    /* OpenCL Data structures */

    cl_platform_id platform;
    cl_device_id device;
    cl_context context;
    cl_program program;
    cl_kernel kernel;    
    cl_command_queue queue;
    cl_mem vec_buffer, result_buffer;

    cl_event prof_event;;

    /* ********************* */

    /* C Data Structures / Data types */
    FILE *program_handle; //Kernel file handle
    char *program_buffer; //Kernel buffer

    float *vec, *non_parallel;
    float result[ARRAY_SIZE];

    size_t program_size; //Kernel file size

    cl_ulong time_start, time_end, total_time;

    int i;
    /* ****************************** */

    /* Errors */
    cl_int err;
    /* ****** */

    non_parallel = (float*)malloc(ARRAY_SIZE * sizeof(float));
    vec          = (float*)malloc(ARRAY_SIZE * sizeof(float));

    //Initialize the vector of floats
    for(i = 0; i < ARRAY_SIZE; i++)
    vec[i] = i + 1;

    /************************* C Function **************************************/
    clock_t start, end;

    start = clock();

    for( i = 0; i < ARRAY_SIZE; i++) 
    {
    non_parallel[i] = vec[i] * vec[i];
    }
    end = clock();
    printf( "Number of seconds: %f\n", (clock()-start)/(double)CLOCKS_PER_SEC );

    free(non_parallel);
    /***************************************************************************/




    clGetPlatformIDs(1, &platform, NULL);//Just want NVIDIA platform
    clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);

    // Context error?
    if(err)
    {
    perror("Cannot create context");
    return 1;
    }

    //Read the kernel file
    program_handle = fopen(PROGRAM_FILE,"r");
    fseek(program_handle, 0, SEEK_END);
    program_size = ftell(program_handle);
    rewind(program_handle);

    program_buffer = (char*)malloc(program_size + 1);
    program_buffer[program_size] = '\0';
    fread(program_buffer, sizeof(char), program_size, program_handle);
    fclose(program_handle);

    //Create the program
    program = clCreateProgramWithSource(context, 1, (const char**)&program_buffer, 
                    &program_size, &err);

    if(err)
    {
    perror("Cannot create program");
    return 1;
    }

    free(program_buffer);

    clBuildProgram(program, 0, NULL, NULL, NULL, NULL);

    kernel = clCreateKernel(program, KERNEL_FUNC, &err);

    if(err)
    {
    perror("Cannot create kernel");
    return 1;
    }

    queue = clCreateCommandQueue(context, device, CL_QUEU_PROFILING_ENABLE, &err);

    if(err)
    {
    perror("Cannot create command queue");
    return 1;
    }

    vec_buffer = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
                sizeof(float) * ARRAY_SIZE, vec, &err);
    result_buffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(float)*ARRAY_SIZE, NULL, &err);

    if(err)
    {
    perror("Cannot create the vector buffer");
    return 1;
    }

    clSetKernelArg(kernel, 0, sizeof(cl_mem), &vec_buffer);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &result_buffer);

    size_t global_size = ARRAY_SIZE;
    size_t local_size = 0;

    clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_size, NULL, 0, NULL, &prof_event);

    clEnqueueReadBuffer(queue, result_buffer, CL_TRUE, 0, sizeof(float)*ARRAY_SIZE, &result, 0, NULL, NULL);
    clFinish(queue);



     clGetEventProfilingInfo(prof_event, CL_PROFILING_COMMAND_START,
           sizeof(time_start), &time_start, NULL);
     clGetEventProfilingInfo(prof_event, CL_PROFILING_COMMAND_END,
           sizeof(time_end), &time_end, NULL);
     total_time += time_end - time_start;

    printf("\nAverage time in nanoseconds = %lu\n", total_time/ARRAY_SIZE);



    clReleaseMemObject(vec_buffer);
    clReleaseMemObject(result_buffer);
    clReleaseKernel(kernel);
    clReleaseCommandQueue(queue);
    clReleaseProgram(program);
    clReleaseContext(context);

    free(vec);

    return 0;
}

内核是:

__kernel void float_sum(__global float* vec,__global float* result){
    int gid = get_global_id(0);
    result[gid] = vec[gid] * vec[gid];
}

现在,结果是:

秒数:0.010000

以纳秒为单位的平均时间 = 140737284

0,1407秒是OpenCL时间内核执行的时间,比C函数还多,对吗?因为我认为 OpenCL 应该比 C 非并行算法最快...

【问题讨论】:

  • 我对这些结果感到非常惊讶,尤其是因为您将 OpenCL 速度除以数组大小。你确定你正在正确地计时代码吗?您使用的是 Windows 还是 Linux?您使用的是什么 GPU?
  • 你在试验这个具体的例子吗?如果您使用 float4 类型,您可以在一次操作中对 4 个值进行点积和求和。我已经在下面回答了,假设您不是在寻找这样的优化,而是在寻找 opencl 指针。您还可以使用平方和公式。

标签: c performance algorithm opencl measurement


【解决方案1】:

在 GPU 上执行并行代码不一定比在 CPU 上执行更快。考虑到除了计算之外,您还必须将数据传入和传出 GPU 内存。

在您的示例中,您正在传输 2 * N 个项目并并行执行 O(N) 操作,这是对 GPU 的非常低效的使用。因此,很可能 CPU 确实对这种特定计算更快。

【讨论】:

  • 是否存在使内核更高效的方法?我也在考虑并行化数据......
  • @facundo:不是这样。恐怕您唯一的选择是在 GPU 上运行更重要的计算。例如,您可以尝试实现矩阵乘法。
  • 这真的没有任何意义。为此仅测量内核执行时间。它不考虑数据传输。
  • 在这项任务中,普通的 gpu 通常会击败高端 cpu。传输时间没有测量,但它只有4M up/down。这个例子开始有点做作。前 N 个平方和 = (N * (N + 1) * (2N + 1)) / 6
【解决方案2】:

仅供其他人向她寻求帮助:使用 OpenCL 分析内核运行时的简短介绍

启用分析模式:

cmdQueue = clCreateCommandQueue(context, *devices, CL_QUEUE_PROFILING_ENABLE, &err);

分析内核:

cl_event prof_event; 
clEnqueueNDRangeKernel(cmdQueue, kernel, 1 , 0, globalWorkSize, NULL, 0, NULL, &prof_event);

读取分析数据:

cl_ulong ev_start_time=(cl_ulong)0;     
cl_ulong ev_end_time=(cl_ulong)0;   

clFinish(cmdQueue);
err = clWaitForEvents(1, &prof_event);
err |= clGetEventProfilingInfo(prof_event, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &ev_start_time, NULL);
err |= clGetEventProfilingInfo(prof_event, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &ev_end_time, NULL);

计算内核执行时间:

float run_time_gpu = (float)(ev_end_time - ev_start_time)/1000; // in usec

你的方法

total_time/ARRAY_SIZE

不是你想要的。它将为您提供每个工作项的运行时间。

以纳秒为单位的操作/时间将为您提供 GFLOPS(每秒千兆浮点操作)。

【讨论】:

    【解决方案3】:

    这是您的应用程序的一大问题:

    size_t global_size = ARRAY_SIZE;
    size_t local_size = 0;
    

    您正在创建单项工作组,这将使大部分 gpu 闲置。在许多情况下,使用单项工作组只会使用 1/15 的 gpu。

    试试这个:

    size_t global_size = ARRAY_SIZE / 250; //important: local_size*global_size must equal ARRAY_SIZE
    size_t local_size = 250; //a power of 2 works well. 250 is close enough, yes divisible by your 1M array size
    

    现在您正在创建能够更好地使图形硬件的 ALU 饱和的大型组。内核将按照您现在的方式运行良好,但也有一些方法可以充分利用内核部分。

    内核优化:将 ARRAY_SIZE 作为附加参数传递到内核中,并使用更少的组和更优化的组大小。您还将消除 local_size*global_size 完全等于 ARRAY_SIZE 的需要。工作项的全局 id 从未在此内核中使用,也不需要它,因为传入了总大小。

    __kernel void float_sum(__global float* vec,__global float* result,__global int count){
      int lId = get_local_id(0);
      int lSize = get_local_size(0);
      int grId = get_group_id(0);
      int totalOps = count/get_num_groups(0);
      int startIndex = grId * totalOps;
      int maxIndex = startIndex+totalOps;
      if(grId == get_num_groups(0)-1){
        endIndex = count;
      }
      for(int i=startIndex+lId;i<endIndex;i+=lSize){
        result[i] = vec[i] * vec[i];
      }
    }
    

    现在您可能会想,对于这样一个简单的内核,有很多变量。请记住,内核的每次执行都会对数据执行多项操作,而不仅仅是一个。使用下面的值,在我的 radeon 5870(20 个计算单元)上,每个工作项最终在其 for 循环中计算 781 或 782 个值。每个组将计算 50000 条数据。我使用的变量的开销远低于创建 4000 个工作组的开销——即 100 万个。

    size_t global_size = ARRAY_SIZE / numComputeUnits;
    size_t local_size = 64; //also try other multiples of 16 or 64 for gpu; or multiples of your core-count for a cpu kernel
    

    See here about how to get the value for numComputeUnits

    【讨论】:

      猜你喜欢
      • 2013-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-05
      • 2014-07-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多