OpenCL学习入门
1.OpenCL概念
OpenCL是一個(gè)為異構(gòu)平臺(tái)編寫(xiě)程序的框架,此異構(gòu)平臺(tái)可由CPU、GPU或其他類(lèi)型的處理器組成。OpenCL由一門(mén)用于編寫(xiě)kernels (在OpenCL設(shè)備上運(yùn)行的函數(shù))的語(yǔ)言(基于C99)和一組用于定義并控制平臺(tái)的API組成。
OpenCL提供了兩種層面的并行機(jī)制:任務(wù)并行與數(shù)據(jù)并行。
名詞的概念:
Platform (平臺(tái)):主機(jī)加上OpenCL框架管理下的若干設(shè)備構(gòu)成了這個(gè)平臺(tái),通過(guò)這個(gè)平臺(tái),應(yīng)用程序可以與設(shè)備共享資源并在設(shè)備上執(zhí)行kernel。實(shí)際使用中基本上一個(gè)廠商對(duì)應(yīng)一個(gè)Platform,比如Intel, AMD都是這樣。
Device(設(shè)備):官方的解釋是計(jì)算單元(Compute Units)的集合。舉例來(lái)說(shuō),GPU是典型的device。Intel和AMD的多核CPU也提供OpenCL接口,所以也可以作為Device。
Context(上下文):OpenCL的Platform上共享和使用資源的環(huán)境,包括kernel、device、memory objects、command queue等。使用中一般一個(gè)Platform對(duì)應(yīng)一個(gè)Context。
Program:OpenCL程序,由kernel函數(shù)、其他函數(shù)和聲明等組成。
Kernel(核函數(shù)):可以從主機(jī)端調(diào)用,運(yùn)行在設(shè)備端的函數(shù)。
Memory Object(內(nèi)存對(duì)象):在主機(jī)和設(shè)備之間傳遞數(shù)據(jù)的對(duì)象,一般映射到OpenCL程序中的global memory。有兩種具體的類(lèi)型:Buffer Object(緩存對(duì)象)和Image Object(圖像對(duì)象)。
Command Queue(指令隊(duì)列):在指定設(shè)備上管理多個(gè)指令(Command)。隊(duì)列里指令執(zhí)行可以順序也可以亂序。一個(gè)設(shè)備可以對(duì)應(yīng)多個(gè)指令隊(duì)列。
NDRange:主機(jī)端運(yùn)行設(shè)備端kernel函數(shù)的主要接口。實(shí)際上還有其他的,NDRange是非常常見(jiàn)的,用于分組運(yùn)算,以后具體用到的時(shí)候就知道區(qū)別了。
2.OpenCL與CUDA的區(qū)別
不同點(diǎn):OpenCL是通用的異構(gòu)平臺(tái)編程語(yǔ)言,為了兼顧不同設(shè)備,使用繁瑣。
CUDA是nvidia公司發(fā)明的專門(mén)在其GPGPU上的編程的框架,使用簡(jiǎn)單,好入門(mén)。
相同點(diǎn):都是基于任務(wù)并行與數(shù)據(jù)并行。
3.OpenCL的編程步驟
(1)Discover and initialize the platforms
調(diào)用兩次clGetPlatformIDs函數(shù),第一次獲取可用的平臺(tái)數(shù)量,第二次獲取一個(gè)可用的平臺(tái)。
(2)Discover and initialize the devices
調(diào)用兩次clGetDeviceIDs函數(shù),第一次獲取可用的設(shè)備數(shù)量,第二次獲取一個(gè)可用的設(shè)備。
(3)Create a context(調(diào)用clCreateContext函數(shù))
上下文context可能會(huì)管理多個(gè)設(shè)備device。
(4)Create a command queue(調(diào)用clCreateCommandQueue函數(shù))
一個(gè)設(shè)備device對(duì)應(yīng)一個(gè)command queue。上下文conetxt將命令發(fā)送到設(shè)備對(duì)應(yīng)的command queue,設(shè)備就可以執(zhí)行命令隊(duì)列里的命令。
(5)Create device buffers(調(diào)用clCreateBuffer函數(shù))
Buffer中保存的是數(shù)據(jù)對(duì)象,就是設(shè)備執(zhí)行程序需要的數(shù)據(jù)保存在其中。
Buffer由上下文conetxt創(chuàng)建,這樣上下文管理的多個(gè)設(shè)備就會(huì)共享Buffer中的數(shù)據(jù)。
(6)Write host data to device buffers(調(diào)用clEnqueueWriteBuffer函數(shù))
(7)Create and compile the program
創(chuàng)建程序?qū)ο?#xff0c;程序?qū)ο缶痛砟愕某绦蛟次募蛘叨M(jìn)制代碼數(shù)據(jù)。
(8)Create the kernel(調(diào)用clCreateKernel函數(shù))
根據(jù)你的程序?qū)ο?#xff0c;生成kernel對(duì)象,表示設(shè)備程序的入口。
(9)Set the kernel arguments(調(diào)用clSetKernelArg函數(shù))
(10)Configure the work-item structure(設(shè)置worksize)
配置work-item的組織形式(維數(shù),group組成等)
(11)Enqueue the kernel for execution(調(diào)用clEnqueueNDRangeKernel函數(shù))
將kernel對(duì)象,以及 work-item參數(shù)放入命令隊(duì)列中進(jìn)行執(zhí)行。
(12)Read the output buffer back to the host(調(diào)用clEnqueueReadBuffer函數(shù))
(13)Release OpenCL resources(至此結(jié)束整個(gè)運(yùn)行過(guò)程)
OpenCL的主要執(zhí)行過(guò)程:
4.說(shuō)明
OpenCL中的核函數(shù)必須單列一個(gè)文件。
OpenCL的編程一般步驟就是上面的13步,太長(zhǎng)了,以至于要想做個(gè)向量加法都是那么困難。
不過(guò)上面的步驟前3步一般是固定的,可以單獨(dú)寫(xiě)在一個(gè).h/.cpp文件中,其他的一般也不會(huì)有什么大的變化。
5.程序?qū)嵗?#xff0c;向量運(yùn)算
5.1通用前3個(gè)步驟,生成一個(gè)文件
tool.h
tool.c
#include <CL/cl.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <iostream> #include <string> #include <fstream> #include "tool.h" using namespace std;/** convert the kernel file into a string */ 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 0;}f.read(str, fileSize);f.close();str[size] = '\0';s = str;delete[] str;return 0;}cout<<"Error: failed to open file\n:"<<filename<<endl;return -1; }/**Getting platforms and choose an available one.*/ int getPlatform(cl_platform_id &platform) {platform = NULL;//the chosen platformcl_uint numPlatforms;//the NO. of platformscl_int status = clGetPlatformIDs(0, NULL, &numPlatforms);if (status != CL_SUCCESS){cout<<"Error: Getting platforms!"<<endl;return -1;}/**For clarity, choose the first available platform. */if(numPlatforms > 0){cl_platform_id* platforms =(cl_platform_id* )malloc(numPlatforms* sizeof(cl_platform_id));status = clGetPlatformIDs(numPlatforms, platforms, NULL);platform = platforms[0];free(platforms);}elsereturn -1; }/**Step 2:Query the platform and choose the first GPU device if has one.*/ cl_device_id *getCl_device_id(cl_platform_id &platform) {cl_uint numDevices = 0;cl_device_id *devices=NULL;cl_int status = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL, &numDevices);if (numDevices > 0) //GPU available.{devices = (cl_device_id*)malloc(numDevices * sizeof(cl_device_id));status = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, numDevices, devices, NULL);}return devices; }5.2核函數(shù)文件
HelloWorld_Kernel.cl
__kernel void helloworld(__global double* in, __global double* out){int num = get_global_id(0);out[num] = in[num] / 2.4 *(in[num]/6) ;}5.3主函數(shù)文件
HelloWorld.cpp
//For clarity,error checking has been omitted. #include <CL/cl.h> #include "tool.h" #include <string.h> #include <stdio.h> #include <stdlib.h> #include <iostream> #include <string> #include <fstream> using namespace std;int main(int argc, char* argv[]) {cl_int status;/**Step 1: Getting platforms and choose an available one(first).*/cl_platform_id platform;getPlatform(platform);/**Step 2:Query the platform and choose the first GPU device if has one.*/cl_device_id *devices=getCl_device_id(platform);/**Step 3: Create context.*/cl_context context = clCreateContext(NULL,1, devices,NULL,NULL,NULL);/**Step 4: Creating command queue associate with the context.*/cl_command_queue commandQueue = clCreateCommandQueue(context, devices[0], 0, NULL);/**Step 5: Create program object */const char *filename = "HelloWorld_Kernel.cl";string sourceStr;status = convertToString(filename, sourceStr);const char *source = sourceStr.c_str();size_t sourceSize[] = {strlen(source)};cl_program program = clCreateProgramWithSource(context, 1, &source, sourceSize, NULL);/**Step 6: Build program. */status=clBuildProgram(program, 1,devices,NULL,NULL,NULL);/**Step 7: Initial input,output for the host and create memory objects for the kernel*/const int NUM=512000;double* input = new double[NUM];for(int i=0;i<NUM;i++)input[i]=i;double* output = new double[NUM];cl_mem inputBuffer = clCreateBuffer(context, CL_MEM_READ_ONLY|CL_MEM_COPY_HOST_PTR, (NUM) * sizeof(double),(void *) input, NULL);cl_mem outputBuffer = clCreateBuffer(context, CL_MEM_WRITE_ONLY , NUM * sizeof(double), NULL, NULL);/**Step 8: Create kernel object */cl_kernel kernel = clCreateKernel(program,"helloworld", NULL);/**Step 9: Sets Kernel arguments.*/status = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&inputBuffer);status = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&outputBuffer);/**Step 10: Running the kernel.*/size_t global_work_size[1] = {NUM};cl_event enentPoint;status = clEnqueueNDRangeKernel(commandQueue, kernel, 1, NULL, global_work_size, NULL, 0, NULL, &enentPoint);clWaitForEvents(1,&enentPoint); ///waitclReleaseEvent(enentPoint);/**Step 11: Read the cout put back to host memory.*/status = clEnqueueReadBuffer(commandQueue, outputBuffer, CL_TRUE, 0, NUM * sizeof(double), output, 0, NULL, NULL);cout<<output[NUM-1]<<endl;/**Step 12: Clean the resources.*/status = clReleaseKernel(kernel);//*Release kernel.status = clReleaseProgram(program); //Release the program object.status = clReleaseMemObject(inputBuffer);//Release mem object.status = clReleaseMemObject(outputBuffer);status = clReleaseCommandQueue(commandQueue);//Release Command queue.status = clReleaseContext(context);//Release context.if (output != NULL){free(output);output = NULL;}if (devices != NULL){free(devices);devices = NULL;}return 0; }編譯、鏈接、執(zhí)行:
g++ -o A *.cpp -lOpenCL ./A總結(jié)
以上是生活随笔為你收集整理的OpenCL学习入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 顺序表归并
- 下一篇: 丁洪波 -- 不要“ 总是拿着微不足道