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

简介: 现在,我们开始写一个简单的OpenCL程序,计算两个数组相加的和,放到另一个数组中去。程序用cpu和gpu分别计算,最后验证它们是否相等。OpenCL程序的流程大致如下: 下面是source code中的主要代码:   int main(int argc, char* ar...

      现在,我们开始写一个简单的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缓冲的完成,waitForEventAndReleae 是一个用户定义的函数,它的内容如下,主要代码就是通过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

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
6月前
|
并行计算 异构计算 Python
python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
【6月更文挑战第3天】python代码torch.device("cuda:0" if torch.cuda.is_available() else "cpu")是什么意思?
612 4
|
7月前
|
达摩院 并行计算 异构计算
modelscope调用的模型如何指定在特定gpu上运行?排除使用CUDA_VISIBLE_DEVICES环境变量
由于个人需要,家里有多张卡,但是我只想通过输入device号的方式,在单卡上运行模型。如果设置环境变量的话我的其他服务将会受影响。
使用Automatic1111在本地PC上运行SDXL 1.0
这是我们部署Stable Diffusion的第三篇文章了,前两篇文章都详细介绍了Automatic1111的stable-diffusion-webui的安装,这次主要介绍如何使用SDXL 1.0模型。
168 0
|
并行计算 PyTorch 算法框架/工具
【PyTorch】cuda()与to(device)的区别
【PyTorch】cuda()与to(device)的区别
306 0
|
并行计算
CUDA 核函数调试:cuda-gdb,cuda printf, cuda assert 【读书笔记】
CUDA 核函数调试:cuda-gdb,cuda printf, cuda assert 【读书笔记】
294 0
CUDA 核函数调试:cuda-gdb,cuda printf, cuda assert 【读书笔记】
|
并行计算 数据可视化 Linux
《CUDA高性能并行计算》----第1章 起 步 1.1 运行CUDA样例程序
本章带领读者进入CUDA并行计算的世界。我们将从运行一个CUDA工具箱(CUDA Toolkit)提供的样例程序开始。这个样例程序包含串行和并行两个版本,因此读者可以通过运行性能上的对比来建立CUDA是如何增强运算能力的第一印象。
4547 0
|
Linux Android开发 Windows
|
openCL 异构计算 编译器
GPGPU OpenCL 获取kernel函数编译信息
  使用OpenCL编程时,kernel写成一个单独的文件或者将文件内容保存在一个string中。可以使用clBuildProgram对kernel进行编译链接(compiles & links),如果失败,可以使用clGetProgramBuildInfo获取OpenCL编译器对kernel的编译信息。
1105 0
|
openCL 异构计算
GPGPU OpenCL Reduction操作与group同步
Reduction操作:规约操作就是由多个数生成一个数,如求最大值、最小值、向量点积、求和等操作,都属于这一类操作。 有大量数据的情况下,使用GPU进行任务并行与数据并行,可以收到可好的效果。 group同步:OpenCL只提供了工作组内的各线程之间的同步机制,并没有提供所有线程的同步。
1183 0