注:本笔记基于互联网上各个博文整合,如有侵权请及时告知。文末有参考博文的链接。
CPU,GPU的架构简介
CPU:多指令单数据流(流水线模式),MISD,擅长逻辑控制。
GPU:单指令多数据流(向量算法),SIMD,擅长并行计算。
所以1个CPU+几个GPGPU(通用并行处理的GPU)的架构即异构编程。
使用通用的OpenCL接口(API)开发的应用可以在不同的SDK中通用,OPenCL只是一个标准,Intel,AMD,NVIDIA等各大厂商采用不同的SDK进行自定制的开发;
OpenCL硬件层的抽象
它是一个Host(控制处置单元,常通由一个CPU担负)和一堆Compute Device(计算设备,由一些GPU、CPU其他支撑的芯片担负),其中Compute Device切分为很多Processing Element(这是独立介入单数据计算的最小单元,这个不同硬件实现都不一样,如GPU可能就是其中一个Processor,而CPU多是一个Core,我猜的。。因为这个实现对开发者是隐藏的),其中很多个Processing Element可以构成组为一个Compute Unit,一个Unit内的element之间可以便利的同享memory,也只有一个Unit内的element可以实现同步等操作。
可以理解为:
Host是公司的总经理:负责对公司的整体任务分配与调度。
Compute Device是公司总经理管理的若干个实施部门:各个这样的部门负责把总经理分配的任务并行地完成。但是,各部门执行同样的运算任务。
Compute Unit是各部门下面的各个项目组,负责把任务在技术层面上实现。各个项目组的技术细节不对外公开(项目组内共享Local Memory)。
Processing element是员工,负责项目组内任务的实施,是最底层的工作节点。员工有自己私有的技术(Private Memory)。整个公司内,每个Processing Element有自己的索引号(Global ID),工号。在每个项目组(Workgroup)内,有自己项目组内的编号,就是LocalID。下图从Compute Device进行划分。
如上图,有全局内存(变量),全局常量,本地内存以及局部变量。
OpenCL的软件层架构:
Setup相关知识(系统的初始化):
-Device:对应一个计算设备;注意,多核的CPU是一个整个的Device。即Device是单个CPU或GPU或FPGA。
-Context:上下文;一个Context包括几个Device,Context是这些Device的联系纽带,只有在同一个Context里的Device才能彼此交流任务。你的单板上可以有很多Context。Context可以由CPU或CPU+GPU创建。
-Command queue:给每个Device提交的指令序列。
内存相关知识:
-Buffer:内存
-Images:原生的用于GPU运算的图像处理的“类型”,表示各种维度的图像。
-数据交互的方式:A 将地址拷贝到工作组空间,计算完成再拷贝出去(传形参);B 将计算数据组的地址传输进去(传指针)。
GPU代码执行相关知识:
-Program:包括Kernel和其他库等。OpenCL是动态编译的语言,源代码编译后生成一个相同的中间文件,该中间文件连接不同的linker时生成不同的程序。然后将程序读入Compute Device。
-kernel:在processing element(PE,员工)处跑的内核程序,是最基本的处理事情。各个PE的内核(算法)相同而处理的数据不同,这就是所谓的单指令多数据体系(SIMD)。
-Work item:processing element在代码里的表示,一个Work Item代表一个processing element(PE,员工)。
-程序对象:内核程序的源代码文件(.cl文件)和可执行文件。内存对象:计算设备执行OpenCL内核程序所需的变量。
并行与同步相关的知识:
-任务并行与数据并行:任务并行是指流水线的工作模式。数据并行是指多组数据同时进行相同计算的方式(所有的矩阵操作基本都可以用!)
-Command queue命令队列:每个计算设备Compute Device都有一个或多个命令队列。但一个命令队列只管理一个计算设备。通过命令队列,可以实现宿主机和计算设备的异步控制。分为启动命令(开始执行kernel程序);内存命令(在host和内存设备之间移动数据,或进行内存映射);同步命令(约束命令在计算设备的执行情况)。
-Events(事件):命令队列中每条命令都有着命令的状态,当命令的状态发生变化,就产生了事件Event。在分布式计算的环境中,Events用于不同计算单元之间的同步。
-按事件发生位置来分:内核端事件:主要负责异步执行命令的同步操作(多个处理单元的阶段同步)和全局内核和本地内存的同步。主机端事件:完成命令队列之间的同步操作(统筹各个计算设备的操作)。
-按事件发生原因来分:
命令事件
l CL_QUEUED:命令已经加入命令队列。
l CL_SUBMITTED:命令已经有宿主机提交给与所在命令队列相关联的设备。
l CL_RUNNING:该命令正在执行。
l CL_COMPLETE:命令已经完成
l ERROR_CORE:负数,指代不同的错误情况。
用户自定义事件
需要进行同一上下文内的各ConputeDevice之间同步时,用用户自定义事件。
cl_event clCreateUserEvent(
cl_context context, //指定上下文
cl_uint* errcode_ret //该函数所关联的错误值
)
如果创建成功,errcode_ret会被赋值为CL_SUCCESS或者错误时,赋值为以下的值:
CL_INVALID_EVENT:上下文不合法
CL_OUT_OF_RESOURCE: 资源未就绪或分配资源失败
CL_OUT_OF_HOST_MEMORY:宿主机资源未就绪或分配资源失败
之后我们就可以在各个处理函数中设置返回的事件的值:
cl_intclSetUserEventStatus(
cl_eventevent, //具体事件值
cl_intexecution_status //指向状态
)
-同步:
OpenCL是基于任务并行,主机控制的模型,其中每一项任务都是数据并行的,具体通过使用和设备关联的线程安全的命令队列来实现。
-单设备同步:通过以下设置:
A设置barrier(即屏障,在组内所有的item没有到达这个barrier之前,所有的item是不向下执行的)。
cl_int clEnqueueBarrier(
cl_command_queuecommand_queue
)
B等待事件(等待事件,即将一个等待事件加入命令队列,只有这个等待事件满足以后,才能执行之后加入的命令)。
C阻塞访问:在内存IO读取时设置一个标志,当标志有效时,会阻塞直到拷贝完成。
-多设备同步:cFinish,等待另一个命令队列执行完成,之后的命令才能继续执行。如一个CPU一个GPU,两者需要互相访问彼此的数据,那么如何实现同步呢,如果CPU要访问CPU的数据,必须等待CPU当前的命令队列执行完成,不再占用内存,GPU才能进行访问。clFinish可以阻塞程序的执行直到命令队列中的所有命令都执行完成。但是这只是相当于在末尾加上了一个barrier。