OPencl学习笔记

上周开始学习opencl实战这本书,因同时在忙本溪的一个路灯的调试项目,进展并不多。现将前两章的学习笔记整理如下:

奋斗软件安装过程: 

首先下载可以编译opencl的软件开发包(software development kit ,SDK).

两种:AMDSDK和Nvidia sdk。根据你的操作系统和具体的硬件配置选择相应的。

安装sdk前要确保两点:1、硬件系统的生产厂商和具体型号以及该款硬件是否支持opencl、2、了解sdk的重要文件和基本知识。

  编译任何C、C++之前都需要相应的头文件和库文件:头文件声明了常数、数据结构和函数;库文件包含了函数的可执行代码【大多数Opencl应用程序只有一个头文件:cl.h,在windows上,cl.h文件位于CL路径下】

  AMD和nvidia都提供了名为OPENCL的库文件(windows 下是OpenCL.dll文件),另外还提供了了一个ICD(可安装的客户驱动)

  在编译OpenCL程序时,需要告诉连接器如何访问主要的OPenCL库文件。但是并不需要指出ICD的名字和路径,这以为这你可以在不知道用户硬件类型的前提下发布你的程序。

  在运行时,应用程序必须能够访问ICD文件,在windows上,和厂商相关的库文件上是设定在注册表之中的,在安装SDK时,它会仿真注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Khronos\OpenCL\Vendors。

  Opencl程序在windows上执行时,它会先查看ICD的文件名,然后加载所需要的对应库文件。故,ICD库文件必须放在连接器的搜索路径下。ICD的默认搜索路径为:C:\Windows\system or

  windows上安装AMD显卡驱动。(起初安成CUDA的GPU驱动了)

  奋斗Chapter1

Java的成功在于“一次编写,各处运行”;OPENCL秉性类似的理念:“一次编写、各设备上运行”因此,你不需要针对特定厂商的硬件,去学习他们的专用编程语言。而C、C++编写的程序只是在特定的编译目标上运行。

  并发——顺序代码通过任务调度在进程(线程)间实现资源分享,并行执行。

并行编程——将不同的运算任务分配给不同的处理单元,同时并行执行。【在opencl中这些任务被称为内核

  

 内核——是针对一个或多个兼容Opencl的设备而特别编写的函数,并通过主机应用程序发送到相应的一个或多个设备上。

  主机应用程序——是一个在我们称为主机的用户开发系统上运行的C/C++应用程序。主机可以将内核程序送到计算机显卡的GPU上执行也可以选择当前的cpu作为执行目标。

主机应用程序通过上下文(context)的容器来管理所连接的设备。主机必须从程序(program)的内核容器中选择函数,才能创建出内核函数,然后再向内核函数中调入相应的参数数据,并发送到指令序列的数据结构中。

指令序列——是主机实现对设备控制的一种机制,而当内核入列时,设备便会执行相应的函数。

  OPenCL应用程序可以配置不同的设备来完成不同的任务,每个任务都是针对不同的数据进行运算处理。(这相对于其他的开发工具只允许数据并行的处理机制(每个设备都收到相同的指令但处理的是整个数据集的不同部分)是一个重要的优势)

可移植性、向量运算、并行编程使得OPenCL和一般的C\C++比较起来更为强大和高效,但随之而来的是计算机系统更大的复杂性。(在实际的OPenCL编程中,创建一系列不同的数据结构并协调它们之间的关系是必不可少的)

OPenCL应用程序:

  主机选择设备并放入上下文中——主机从程序中选择内核,并将其发送到指令序列中——每个设备运行指令序列中的内核程序——主机接受来自设备的事件,并调用事件处理例程——所有设备完成运算处理之后,主机接受、处理结果输出。

框架和SDK:框架包括平台层(通过它来访问设备,设定上下文)、运行时(让主机应用程序将内核和指令序列发送到上下文的设备上)、编译器(构建含有可执行内核的程序)。

 奋斗Chapter2

二、主机编程:基本的数据结构

六种基本的opencl数据结构::平台、设备、上下文、程序、内核和命令队列。

每一种数据结构,我们都要考察两种函数:一是创建数据结构的函数;二是提供被创建数据结构信息的函数。

处理器和操作系统的区别在于保存基本数据方式上的不同。

Cl_char 8(位宽)

Cl_uchar 8

Cl_short 16

Cl_ushort 16

Cl_int 32

Cl_uint 32

Cl_long 64

Cl_ulong 64

Cl_half 16

Cl_float 32

Cl_double 64

一、获取平台信息cl_platform_id.每一个cl_platform_id 结构对应的是安装在主机上的不同的OpenCL d的具体实现(叫做平台)  

编写平台程序一般分为两步:首先是为一个或多个cl_platform_id 结构分配空间;其次是调用函数clGetPlatformID 来初始化这些数据结构。

Cl_intGetplatformIDs(cl_uint num_entries, cl_platform_id *platforms, cl_uint *num_platforms)

函数的作用是:将cl_platform_id放到platforms 所指向的内存空间中,把可用的平台数用num_platforms 来保存,而函数返回一个整数,来检测是否成功检测到一个或多个平台。返回值为0表示检测成功,返回负数则表示检测失败。

区别num_entries 和num_platforms 的含义:前者表示的是你想要检测的平台数的上限值,它将限制被放到platforms数组中的cl_platform_id的数量,如果将num_entries 设置为0,则函数将报错。后者则反映了主机上能够检测到的平台的数量,这个参数将会在运行的时候由函数来设定。

当在不同的开发系统上,针对不同的平台1.1创建数据结构cl_platform_id 时,你必须提前知道系统所安装的平台数。而要达到这个目的,你就需要将参数platforms 设置为null、并调用函数clGetPlatformIDs.将安装平台数的结果保存在num_platforms中,然后在分配数组空间时,调用clGetPlatformIDs完成对cl_platform_id的初始化。

  代码如下:函数调用——内存分配——函数调用 的模式。

 cl_platform_id *platforms;

 cl_uint num_platforms;

 clGetplatformIDs(5,NULL,&num_platforms);//将平台数保存在num_platforms中

 platforms=(cl_platform_id*)

  mall(sizeof(cl_platform_id) * num_platforms);

 clGetplatformIDs(num_platforms,platforms,NULL);//cl_platform_id 保存在platforms数组中。

1.2获取平台信息需调用clGetplatformInfo函数。

二、访问安装设备cl_device_id,主要包含两个函数:cl_GetDeviceIDs和clGetDeviceInfo

2.1创建设备结构:通过clGetdeviceIDs函数将Opencl设备所对用的结构保存在cl_device_id 型数组中。

OPenCL的设备类型:

CL_DEVICE_TYPE_ALL和平台相关的所有设备

CL_DEVICE_TYPE_DEFAULT和平台默认类型相关的设备

CL_DEVICE_TYPE_CPU主机处理器

CL_DEVICE_TYPE_GPU包含GPU的设备

CL_DEVICE_TYPE_ACCELERATOR外部设备用来加速运算

2.2获取设备信息:clGetDeviceInfo

  设备的信息参数:

  CL_DEVICE_NAME返回设备的名字

  CL_DEVICE_AVAILABLE返回设备是否可用

  CL_DEVICE_EXTENSIONS返回设备所支持的OpenCL扩展

;设备扩展和平台扩展不是一回事。设备扩展对应的是设备所能完成的操作和设备所能处理的数据。

三、通过上下文管理设备

上下文是命令队列创建的基础,而命令队列是主机和设备间通信的纽带。

平台不同,上下文也不同、但主机程序可以通过多个上下文来管理设备,甚至可以在一个平台上创建多个上下文。

3.1创建上下文。

上下文表示:cl_context.通过clCreatContext或者clCreatContextFromType来创建(使用它的好处是创建上下文的过程中,并不需要访问平台和设备,。同时,它们返回的并非错误代码二是cl_context)。

区别cl_context_properties指针和志向用户数据的void指针很重要。User_data 指针可指向任何类型的数据。它的作用是提供报错信息。而properties指针指向的由一个属性名和属性值所组成的数组。并且数组以0元素结尾。属性名和属性值是一一对应的。

3.2获取上下文信息

 clGetContextInfo通过clGetplatformInfo和clGetDeviceInfo实现。

3.3上下文和引用计数

 保留计数——如果cl_context被生命为局部变量,它的内存便会在其所在的函数将要结束的时候自动释放。,而第三方软件库中的程序还要在其所在的函数结束之后继续访问cl_context。那么Opencl基于此原因,对cl_context的被访问次数进行管理。这个访问次数就是保留计数(在创建cl_context的时候,被初始化为1,计数为0时,程序释放空间)。

改变计数的函数:clRetainContext和clReleaseContext。(如果编写函数,创建CL_context结构,一定要注意在函数结尾调用clReleaseContext)将计数建委0

四、检查设备的引用计数。很多opencl的数据结构只有在上下文可用后才被创建。

五、将设备代码保存在程序中。

程序和内核的区别:虽然都是可执行代码、但内核表示的只是设备上执行的某个函数而程序则是由一堆的内核所组成。

Opencl中一个程序由数据结构cl_program表示。

5.1创建程序。

  Opencl中有两个函数负责创建新的程序:clCreatProgramWithSource和clCreatProgramWithBinary.(注:它们呢不接受文件名和文件句柄作为参数输入,故,需要在调用这两个函数转换前预先将保存在文件中的内核代码通过文件操作督导缓存中,如果程序所包含的的内核代码来自多个文件,则还需要预先创建一个缓存数组)

  编译——只是将源代码转换为目标代码。

  构建——包含了检查文件依赖、编译、链接、生成可执行程序等操作,可以通过makefile这样的构建脚本来理解编译和构建的区别。编译只是其中一步,而构建是整个过程。

如何通过单个文本创建cl_program。整个过程分为三步::确定文件kernel.cl的大小,二:将文件内容读到缓存之中,最后一步是通过缓存创建cl_program结构。

clCreatProgramWithBinary:字符串来源是二进制文件

clCreatProgrammWithSource:是从文本文件中读取字符串

5.2编译程序

Opencl标准要求每个编译器都必须通过函数clBuildProgram进行访问,这个函数可以针对同一平台的不同设备,编译和链接cl_program结构,函数并不会返回新的cl_program结构但会对输入做修改。

程序编译选项:

-DNAME 将宏NAME设置为1。

-Idir 包含头文件所在的路径

-w抑制警告

:::

想要了解编译出错错在何处就要通过访问生成日志(build log)

5.3获取程序信息

通过调用clGetProgramInfo(提供和程序相关的数据结构的信息,如上下文和设备信息)和clGetProgramBuildInfo来访问相关信息(即程序的编译信息,非常重要,是了解程序构建过程的唯一方法)。

5.4构建来自多个源文件的程序

确定原文件大小——将源文件读到缓存正——创建程序对象——构建程序——确定生成日志大小——读取生成日志

六、将函数打包成内核。

 程序编译和链接之后,需要将函数打包成名为内核的数据结构。

使用内核的好处是他们都是可部署的。内核是卡牌,设备是玩家,那命令队列便是玩家手中的卡牌。

各内核都可以用cl_kernel结构来表示。

6.1创建内核opencl定义了两个函数通过cl_program创建cl_kernel结构,这两个函数都容易使用,但clCreatKernelsInProgram更为简单,他为程序中的每个函数创建内核。clCreatKernel可以创建单个内核,且需要知道创建内核所在的函数名,它只能返回一个cl_kernel结构。需要创建多个内核时候,便需要反复调用函数clCreatKernel。

Char kernel_name[]=”convole”;

Kernel=clCreatKernel(program,kernel_name,&error);//函数clCreatKernel将会检查程序,确定是否由一个定义为convole的函数,如果不存在,则返回NULL,如果存在,参数error将被设CL_INVALID_KERNEL_NAME。

6.2获取内核信息

clGetKernelInfo

七、用命令队列保存内核

创建内核不需要确定目标设备。但若要创建命令队列,则必须要确定目标设备。确定目标之后,当再将内核部署到命令队列之中时,所有的内核便会被发送到与这个队列相关联的设备上执行。

命令队列中的命令只有一个方向:主机——设备

7.1创建命令队列(cl_command_queue)

通过clCreatCommandQueue创建新的队列。

Cl_command_queue_properties包含两种:CL_QUEUE_PROFLING_ENABLE(使能性能分析事件)

CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE使能命令队列的乱序执行。

7.2入列内核执行命令

clEnqueue开头的函数都是通过命令队列向设备发送命令。

八、总结:主要讲述主机编程。

步骤:创建一个或多个cl_platform_id结构——使用这些平台找到所关联的设备,各设备用cl_device_id结构表示。同时,应用程序可通过clGetDeviceInfo来找到和设备相关的信息,一旦确定了目标设备,主机用用程序会将他们关联到cl_context结构中。——主机应用程序读入内核函数的源程序或二进制代码,通过代码创建cl_program结构。然后调用clBuildProgram来构建整个程序。主机应用程序会面向上下文中的每个设备来编译这些代码。一旦程序构建承诺成功,主机应用程序会为其所包含的函数构建cl_kernel结构。为了使得主机和设备间通信,主机应用程序将创建cl_command_queue的结构,并将各条命令发送到队列中。每条命令通知对应的目标设备完成相应的操作。

猜你喜欢

转载自blog.csdn.net/qq_34018578/article/details/70194146