OpenCL 学习step by step (2) 一个简单的OpenCL的程序

现在,我们开始写一个简单的OpenCL程序,计算两个数组相加的和,放到另一个数组中去。程序用cpu和gpu分别计算,最后验证它们是否相等。OpenCL程序的流程大致如下:

image

下面是source code中的主要代码:

 

int main(int argc, char* argv[]) 
    { 
    //在host内存中创建三个缓冲区 
    float *buf1 = 0; 
    float *buf2 = 0; 
    float *buf = 0;

    buf1 =(float *)malloc(BUFSIZE * sizeof(float)); 
    buf2 =(float *)malloc(BUFSIZE * sizeof(float)); 
    buf =(float *)malloc(BUFSIZE * sizeof(float));

    //用一些随机值初始化buf1和buf2的内容 
    int i; 
    srand( (unsigned)time( NULL ) ); 
    for(i = 0; i < BUFSIZE; i++) 
        buf1[i] = rand()%65535;

    srand( (unsigned)time( NULL ) +1000); 
    for(i = 0; i < BUFSIZE; i++) 
        buf2[i] = rand()%65535;

    //cpu计算buf1,buf2的和 
    for(i = 0; i < BUFSIZE; i++) 
        buf[i] = buf1[i] + buf2[i];

    cl_uint status; 
    cl_platform_id platform;

    //创建平台对象 
    status = clGetPlatformIDs( 1, &platform, NULL );

      注意:如果我们系统中安装不止一个opencl平台,比如我的os中,有intel和amd两家opencl平台,用上面这行代码,有可能会出错,因为它得到了intel的opencl平台,而intel的平台只支持cpu,而我们后面的操作都是基于gpu,这时我们可以用下面的代码,得到AMD的opencl平台。 
 

cl_uint numPlatforms;
 std::string platformVendor; 
 status = clGetPlatformIDs(0, NULL, &numPlatforms);
 if(status != CL_SUCCESS)
     {
     return 0;
     }
 if (0 < numPlatforms) 
     {
     cl_platform_id* platforms = new cl_platform_id[numPlatforms];
     status = clGetPlatformIDs(numPlatforms, platforms, NULL);

     char platformName[100];
     for (unsigned i = 0; i < numPlatforms; ++i) 
         {
         status = clGetPlatformInfo(platforms[i],
             CL_PLATFORM_VENDOR,
             sizeof(platformName),
             platformName,
             NULL);

         platform = platforms[i];
         platformVendor.assign(platformName);

         if (!strcmp(platformName, "Advanced Micro Devices, Inc.")) 
             {
             break;
             }
         }

     std::cout << "Platform found : " << platformName << "\n";
     delete[] platforms;
     }


    cl_device_id device;

    //创建GPU设备 
    clGetDeviceIDs( platform, CL_DEVICE_TYPE_GPU, 
        1, 
        &device, 
        NULL); 
    //创建context 
    cl_context context = clCreateContext( NULL, 
        1, 
        &device, 
        NULL, NULL, NULL); 
    //创建命令队列 
    cl_command_queue queue = clCreateCommandQueue( context, 
        device, 
        CL_QUEUE_PROFILING_ENABLE, NULL ); 
    //创建三个OpenCL内存对象,并把buf1的内容通过隐式拷贝的方式 
    //buf1内容拷贝到clbuf1,buf2的内容通过显示拷贝的方式拷贝到clbuf2
 
    cl_mem clbuf1 = clCreateBuffer(context, 
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, 
        BUFSIZE*sizeof(cl_float),buf1, 
        NULL );

    cl_mem clbuf2 = clCreateBuffer(context, 
        CL_MEM_READ_ONLY , 
        BUFSIZE*sizeof(cl_float),NULL, 
        NULL );

   cl_event writeEvt;

    status = clEnqueueWriteBuffer(queue, clbuf2, 1, 
        0, BUFSIZE*sizeof(cl_float), buf2, 0, 0, 0);

    上面这行代码把buf2中的内容拷贝到clbuf2,因为buf2位于host端,clbuf2位于device端,所以这个函数会执行一次host到device的传输操作,或者说一次system memory到video memory的拷贝操作,所以我在该函数的后面放置了clFush函数,表示把command queue中的所有命令提交到device(注意:该命令并不保证命令执行完成),所以我们调用函数waitForEventAndRelease来等待write缓冲的完成,waitForEventAndRelea是一个用户定义的函数,它的内容如下,主要代码就是通过event来查询我们的操作是否完成,没完成的话,程序就一直block在这行代码处,另外我们也可以用opencl中内置的函数clWaitForEvents来代替clFlush和waitForEventAndReleae。

//等待事件完成
int waitForEventAndRelease(cl_event *event)
    {
    cl_int status = CL_SUCCESS;
    cl_int eventStatus = CL_QUEUED;
    while(eventStatus != CL_COMPLETE)
        {
        status = clGetEventInfo(
            *event, 
            CL_EVENT_COMMAND_EXECUTION_STATUS, 
            sizeof(cl_int),
            &eventStatus,
            NULL);
        }

    status = clReleaseEvent(*event);

    return 0;
    }

     status = clFlush(queue); 
     //等待数据传输完成再继续往下执行 
     waitForEventAndRelease(&writeEvt);
 

    cl_mem buffer = clCreateBuffer( context, 
        CL_MEM_WRITE_ONLY, 
        BUFSIZE * sizeof(cl_float), 
        NULL, NULL );

      kernel文件中放的是gpu中执行的代码,它被放在一个单独的文件add.cl中,本程序中kernel代码非常简单,只是执行两个数组相加。kernel的代码为:

__kernel void vecadd(__global const float* A, __global const float* B, __global float* C)
{
    int id = get_global_id(0);
    C[id] = A[id] + B[id];
}

   //kernel文件为add.cl 
    const char * filename  = "add.cl"; 
    std::string  sourceStr; 
    status = convertToString(filename, sourceStr);

convertToString也是用户定义的函数,该函数把kernel源文件读入到一个string中,它的代码如下:

//把文本文件读入一个string中,用来读入kernel源文件
int convertToString(const char *filename, std::string& s)
    {
    size_t size;
    char*  str;

    std::fstream f(filename, (std::fstream::in | std::fstream::binary));

    if(f.is_open())
        {
        size_t fileSize;
        f.seekg(0, std::fstream::end);
        size = fileSize = (size_t)f.tellg();
        f.seekg(0, std::fstream::beg);

        str = new char[size+1];
        if(!str)
            {
            f.close();
            return NULL;
            }

        f.read(str, fileSize);
        f.close();
        str[size] = '\0';

        s = str;
        delete[] str;
        return 0;
        }
    printf("Error: Failed to open file %s\n", filename);
    return 1;
    }


    const char * source    = sourceStr.c_str(); 
    size_t sourceSize[]    = { strlen(source) };

    //创建程序对象 
    cl_program program = clCreateProgramWithSource( 
        context, 
        1, 
        &source, 
        sourceSize, 
        NULL); 
    //编译程序对象 
    status = clBuildProgram( program, 1, &device, NULL, NULL, NULL ); 
    if(status != 0) 
        { 
        printf("clBuild failed:%d\n", status); 
        char tbuf[0x10000]; 
        clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0x10000, tbuf, NULL); 
        printf("\n%s\n", tbuf); 
        return -1; 
        }

    //创建Kernel对象 
    cl_kernel kernel = clCreateKernel( program, "vecadd", NULL ); 
    //设置Kernel参数 
    cl_int clnum = BUFSIZE; 
    clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*) &clbuf1); 
    clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*) &clbuf2); 
    clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*) &buffer);

注意:在执行kernel时候,我们只设置了global work items数量,没有设置group size,这时候,系统会使用默认的work group size,通常可能是256之类的。

    //执行kernel,Range用1维,work itmes size为BUFSIZE 
    cl_event ev; 
    size_t global_work_size = BUFSIZE; 
    clEnqueueNDRangeKernel( queue, 
        kernel, 
        1, 
        NULL, 
        &global_work_size, 
        NULL, 0, NULL, &ev); 
   status = clFlush( queue ); 
   waitForEventAndRelease(&ev); 

    //数据拷回host内存 
    cl_float *ptr;

    cl_event mapevt; 
    ptr = (cl_float *) clEnqueueMapBuffer( queue, 
        buffer, 
        CL_TRUE, 
        CL_MAP_READ, 
        0, 
        BUFSIZE * sizeof(cl_float), 
        0, NULL, NULL, NULL );

   status = clFlush( queue ); 
   waitForEventAndRelease(&mapevt);
 

    
    //结果验证,和cpu计算的结果比较 
    if(!memcmp(buf, ptr, BUFSIZE)) 
        printf("Verify passed\n"); 
    else printf("verify failed");

    if(buf) 
        free(buf); 
    if(buf1) 
        free(buf1); 
    if(buf2) 
        free(buf2);

      程序结束后,这些opencl对象一般会自动释放,但是为了程序完整,养成一个好习惯,这儿我加上了手动释放opencl对象的代码。

    //删除OpenCL资源对象 
    clReleaseMemObject(clbuf1); 
    clReleaseMemObject(clbuf2); 
    clReleaseMemObject(buffer); 
    clReleaseProgram(program); 
    clReleaseCommandQueue(queue); 
    clReleaseContext(context); 
    return 0; 
    }

程序执行后的界面如下:

image

完整的代码请参考:

工程文件gclTutorial1

代码下载:

http://files.cnblogs.com/mikewolf2002/gclTutorial.zip

下面是一个简单OpenCL 示例程序,用于将一个数组中的元素求和: ``` #include <stdio.h> #include <stdlib.h> #include <CL/cl.h> #define MAX_SOURCE_SIZE (0x100000) int main() { cl_platform_id platform_id = NULL; cl_device_id device_id = NULL; cl_uint num_devices, num_platforms; cl_int ret; // Step 1: Get the platform and device information ret = clGetPlatformIDs(1, &platform_id, &num_platforms); ret = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_GPU, 1, &device_id, &num_devices); // Step 2: Create a context cl_context context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &ret); // Step 3: Create a command queue cl_command_queue queue = clCreateCommandQueue(context, device_id, 0, &ret); // Step 4: Create the kernel program from source code FILE *fp; char fileName[] = "./sum.cl"; char *source_str; size_t source_size; fp = fopen(fileName, "r"); if (!fp) { printf("Failed to load kernel\n"); exit(1); } source_str = (char*) malloc(MAX_SOURCE_SIZE); source_size = fread(source_str, 1, MAX_SOURCE_SIZE, fp); fclose(fp); cl_program program = clCreateProgramWithSource(context, 1, (const char **)&source_str, (const size_t *)&source_size, &ret); // Step 5: Build the kernel program ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL); // Step 6: Create the kernel object cl_kernel kernel = clCreateKernel(program, "sum", &ret); // Step 7: Set the kernel arguments int n = 1000; int *A = (int*) malloc(n*sizeof(int)); int sum = 0; for (int i = 0; i < n; ++i) { A[i] = i; sum += i; } cl_mem bufA = clCreateBuffer(context, CL_MEM_READ_ONLY, n*sizeof(int), NULL, &ret); cl_mem bufC = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(int), NULL, &ret); ret = clEnqueueWriteBuffer(queue, bufA, CL_TRUE, 0, n*sizeof(int), A, 0, NULL, NULL); ret = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&bufA); ret = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&bufC); // Step 8: Execute the kernel on the device size_t global_item_size = n; size_t local_item_size = 64; ret = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &global_item_size, &local_item_size, 0, NULL, NULL); // Step 9: Read the result back to the host int result; ret = clEnqueueReadBuffer(queue, bufC, CL_TRUE, 0, sizeof(int), &result, 0, NULL, NULL); printf("Sum using OpenCL: %d\n", result); printf("Sum using CPU: %d\n", sum); // Step 10: Clean up ret = clFlush(queue); ret = clFinish(queue); ret = clReleaseKernel(kernel); ret = clReleaseProgram(program); ret = clReleaseMemObject(bufA); ret = clReleaseMemObject(bufC); ret = clReleaseCommandQueue(queue); ret = clReleaseContext(context); free(A); return 0; } ``` 这个程序包含以下步骤: 1. 获取平台和设备信息。 2. 创建一个 OpenCL 上下文。 3. 创建一个 OpenCL 命令队列。 4. 从源代码中创建一个内核程序。 5. 编译内核程序。 6. 创建内核对象。 7. 设置内核参数。 8. 在设备上执行内核。 9. 从设备上读取结果。 10. 清理资源。 需要注意的是,程序中使用了一个名为 `sum.cl` 的内核代码文件。这个文件的内容如下: ``` __kernel void sum(__global const int *A, __global int *C) { int i = get_global_id(0); if (i < 1000) { C[0] = 0; atomic_add(&C[0], A[i]); } } ``` 这个内核代码用于将一个长度为 1000 的整型数组中的元素求和,并将结果写入一个整型变量中。程序中使用了 `clEnqueueNDRangeKernel` 函数来执行内核,并使用 `clEnqueueReadBuffer` 函数将结果从设备上读取到主机内存中。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值