背景

  • Read the fucking official documents! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  • 对不起,我竟然用了一个夺人眼球的标题;
  • 我会尽量从一个程序员的角度来阐述OpenCL,目标是浅显易懂,如果没有达到这个效果,就当我没说这话;
  • 子曾经曰过:不懂Middleware的系统软件工程师,不是一个好码农;

1. 介绍

  • OpenCL(Open Computing Language,开放计算语言):

    从软件视角看,它是用于异构平台编程的框架;

    从规范视角看,它是异构并行计算的行业标准,由Khronos Group来维护;
  • 异构平台包括了CPU、GPU、FPGA、DSP,以及最近几年流行的各类AI加速器等;
  • OpenCL包含两部分:

    1)用于编写运行在OpenCL device上的kernels的语言(基于C99);

    2)OpenCL API,至于Runtime的实现交由各个厂家,比如Intel发布的opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz

 以人工智能场景为例来理解一下,假如在某个AI芯片上跑人脸识别应用,CPU擅长控制,AI processor擅长计算,软件的flow就可以进行拆分,用CPU来负责控制视频流输入输出前后处理,AI processor来完成深度学习模型运算完成识别,这就是一个典型的异构处理场景,如果该AI芯片的SDK支持OpenCL,那么上层的软件就可以基于OpenCL进行开发了。

话不多说,看看OpenCL的架构吧。

2. OpenCL架构

OpenCL架构,可以从平台模型、内存模型、执行模型、编程模型四个角度来展开。

2.1 Platform Model

平台模型:硬件拓扑关系的抽象描述

  • 平台模型由一个Host连接一个或多个OpenCL Devices组成;
  • OpenCL Device,可以划分成一个或多个计算单元Compute Unit(CU)
  • CU可以进一步划分成一个或多个处理单元Processing Unit(PE),最终的计算由PE来完成;
  • OpenCL应用程序分成两部分:host代码和device kernel代码,其中Host运行host代码,并将kernel代码以命令的方式提交到OpenCL devices,由OpenCL device来运行kernel代码;

2.2 Execution Model

执行模型:Host如何利用OpenCL Device的计算资源完成高效的计算处理过程

Context

OpenCL的Execution Model由两个不同的执行单元定义:1)运行在OpenCL设备上的kernel;2)运行在Host上的Host program;

其中,OpenCL使用Context代表kernel的执行环境:

Context包含以下资源:

  • Devices:一个或多个OpenCL设备;
  • Kernel Objects:OpenCL Device的执行函数及相关的参数值,通常定义在cl文件中;
  • Program Objects:实现kernel的源代码和可执行程序,每个program可以包含多个kernel;
  • Memory Objects:Host和OpenCL设备可见的变量,kernel执行时对其进行操作;

NDrange

  • kernel是Execution Model的核心,放置在设备上执行,当kernel执行前,需要创建一个索引空间NDRange(一维/二维/三维);
  • 执行kernel实例的称为work-item,work-item组织成work-group,work-group组织成NDRange,最终将NDRange映射到OpenCL Device的计算单元上;

有两种方式来找到work-item:

  1. 通过work-item的全局索引;
  2. 先查找到所在work-group的索引号,再根据局部索引号确定;

以一维为例:

  • 上图中总共有四个work-group,每个work-group包含四个work-item,所以local_size的大小为4,而local_id都是从0开始重新计数;
  • global_size代表总体的大小,也就是16个work-item,而global_id则是从0开始计数;

以二维为例:

  • 二维的计算方式与一维类似,也是结合global和local的size,可以得出global_id和local_id的大小,细节不表了;

三维的方式也类似,略去。

2.3 Memory Model

内存模型:Host和OpenCL Device怎么来看待数据

OpenCL的内存模型中,包含以下几类类型的内存:

  • Host memory:Host端的内存,只能由Host直接访问;
  • Global Memory:设备内存,可以由Host和OpenCL Device访问,允许Host的读写操作,也允许OpenCL Device中PE读写,Host负责该内存中Buffer的分配和释放;
  • Constant Global Memory:设备内存,允许Host进行读写操作,而设备只能进行读操作,用于传输常量数据;
  • Local Memory:单个CU中的本地内存,Host看不到该区域并无法对其操作,该区域允许内部的PE进行读写操作,也可以用于PE之间的共享,需要注意同步和并发问题;
  • Private Memory:PE的私有内存,Host与PE之间都无法看到该区域;

2.4 Programming Model

  • 在编程模型中,有两部分代码需要编写:一部分是Host端,一部分是OpenCL Device端;
  • 编程过程中,核心是要维护一个Context,代表了整个Kernel执行的环境;
  • 从cl源代码中创建Program对象并编译,在运行时创建Kernel对象以及内存对象,设置好相关的参数和输入之后,就可以将Kernel送入到队列中执行,也就是Launch kernel的流程;
  • 最终等待运算结束,获取计算结果即可;

3. 编程流程

  • 上图为一个OpenCL应用开发涉及的基本过程;

下边来一个实际的代码测试跑跑,Talk is cheap, show me the code!

4. 示例代码

  • 测试环境:Ubuntu16.04,安装Intel CPU OpenCL SDK(opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz);
  • 为了简化流程,示例代码都不做容错处理,仅保留关键的操作;
  • 整个代码的功能是完成向量的加法操作;

4.1 Host端程序

#include <iostream>
#include <fstream>
#include <sstream> #include <CL/cl.h> const int DATA_SIZE = 10; int main(void)
{
/* 1. get platform & device information */
cl_uint num_platforms;
cl_platform_id first_platform_id;
clGetPlatformIDs(1, &first_platform_id, &num_platforms); /* 2. create context */
cl_int err_num;
cl_context context = nullptr;
cl_context_properties context_prop[] = {
CL_CONTEXT_PLATFORM,
(cl_context_properties)first_platform_id,
0
};
context = clCreateContextFromType(context_prop, CL_DEVICE_TYPE_CPU, nullptr, nullptr, &err_num); /* 3. create command queue */
cl_command_queue command_queue;
cl_device_id *devices;
size_t device_buffer_size = -1; clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, nullptr, &device_buffer_size);
devices = new cl_device_id[device_buffer_size / sizeof(cl_device_id)];
clGetContextInfo(context, CL_CONTEXT_DEVICES, device_buffer_size, devices, nullptr);
command_queue = clCreateCommandQueueWithProperties(context, devices[0], nullptr, nullptr);
delete [] devices; /* 4. create program */
std::ifstream kernel_file("vector_add.cl", std::ios::in);
std::ostringstream oss; oss << kernel_file.rdbuf();
std::string srcStdStr = oss.str();
const char *srcStr = srcStdStr.c_str();
cl_program program;
program = clCreateProgramWithSource(context, 1, (const char **)&srcStr, nullptr, nullptr); /* 5. build program */
clBuildProgram(program, 0, nullptr, nullptr, nullptr, nullptr); /* 6. create kernel */
cl_kernel kernel;
kernel = clCreateKernel(program, "vector_add", nullptr); /* 7. set input data && create memory object */
float output[DATA_SIZE];
float input_x[DATA_SIZE];
float input_y[DATA_SIZE];
for (int i = 0; i < DATA_SIZE; i++) {
input_x[i] = (float)i;
input_y[i] = (float)(2 * i);
} cl_mem mem_object_x;
cl_mem mem_object_y;
cl_mem mem_object_output;
mem_object_x = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * DATA_SIZE, input_x, nullptr);
mem_object_y = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float) * DATA_SIZE, input_y, nullptr);
mem_object_output = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(float) * DATA_SIZE, nullptr, nullptr); /* 8. set kernel argument */
clSetKernelArg(kernel, 0, sizeof(cl_mem), &mem_object_x);
clSetKernelArg(kernel, 1, sizeof(cl_mem), &mem_object_y);
clSetKernelArg(kernel, 2, sizeof(cl_mem), &mem_object_output); /* 9. send kernel to execute */
size_t globalWorkSize[1] = {DATA_SIZE};
size_t localWorkSize[1] = {1};
clEnqueueNDRangeKernel(command_queue, kernel, 1, nullptr, globalWorkSize, localWorkSize, 0, nullptr, nullptr); /* 10. read data from output */
clEnqueueReadBuffer(command_queue, mem_object_output, CL_TRUE, 0, DATA_SIZE * sizeof(float), output, 0, nullptr, nullptr);
for (int i = 0; i < DATA_SIZE; i++) {
std::cout << output[i] << " ";
}
std::cout << std::endl; /* 11. clean up */
clRetainMemObject(mem_object_x);
clRetainMemObject(mem_object_y);
clRetainMemObject(mem_object_output);
clReleaseCommandQueue(command_queue);
clReleaseKernel(kernel);
clReleaseProgram(program);
clReleaseContext(context); return 0;
}

4.2 OpenCL Kernel函数

  • 在Host程序中,创建program对象时会去读取kernel的源代码,本示例源代码位于:vector_add.cl文件中

内容如下:

__kernel void vector_add(__global const float *input_x,
__global const float *input_y,
__global float *output)
{
int gid = get_global_id(0); output[gid] = input_x[gid] + input_y[gid];
}

4.3 输出

参考

The OpenCL Specification

欢迎关注公众号,不定期分享技术文章

一文说清OpenCL框架的更多相关文章

  1. Android平台利用OpenCL框架实现并行开发初试

    http://www.cnblogs.com/lifan3a/articles/4607659.html 在我们熟知的桌面平台,GPU得到了极为广泛的应用,小到各种电子游戏,大到高性能计算,多核心.高 ...

  2. 一文说清 KubeSphere 容器平台的价值

    KubeSphere 作为云原生家族 后起之秀,开源近两年的时间以来收获了诸多用户与开发者的认可.本文通过大白话从零诠释 KubeSphere 的定位与价值,以及不同团队为什么会选择 KubeSphe ...

  3. 一文说清 InnoDB 的事务机制

    我们从一个转账的故事开始. 隔壁小王从美团上找到了一家水饺店,准备中午吃水饺.下单成功,支付20元. 商家这里响了一下:叮叮,您有美团外卖新订单啦,请及时处理.水饺一份,好嘞,下锅. 很快小王吃到外卖 ...

  4. macOS的OpenCL高性能计算

    随着深度学习.区块链的发展,人类对计算量的需求越来越高,在传统的计算模式下,压榨GPU的计算能力一直是重点. NV系列的显卡在这方面走的比较快,CUDA框架已经普及到了高性能计算的各个方面,比如Goo ...

  5. 《开源框架那些事儿22》:UI框架设计实战

    UI是User Interface的缩写.通常被觉得是MVC中View的部分,作用是提供跟人机交互的可视化操作界面. MVC中Model提供内容给UI进行渲染,用户通过UI框架产生响应,一般而言会由控 ...

  6. jQuery和MVVM类框架的编程区别点

    本文说的mvvm框架以react为列,其他应该也是类似的: react实际上仅仅是帮助我们再View层简化,让我们仅仅需要专注数据,只要数据改变,所有的视图就会自己跟随着改变, 本人自己做react项 ...

  7. OpenCL学习笔记(三):OpenCL安装,编程简介与helloworld

    欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 技术交流QQ群:433250724,欢迎对算法.技术.应用感兴趣的同学加入. OpenCL安装 安装我不打算 ...

  8. CUDA与OpenCL架构

    CUDA与OpenCL架构 目录 CUDA与OpenCL架构 目录 1 GPU的体系结构 1.1 GPU简介 1.2 GPU与CPU的差异 2 CUDA架构 2.1 硬件架构 2.1.1 GPU困境 ...

  9. OpenCV、OpenCL、OpenGL、OpenPCL

    对于几个开源库的总结,作为标记,以前看过,现在开始重视起来!更详细资料请移步 开源中国社区! 涉及:OpenCV,OpenCL,OpenGL,OpenPCL 截止到目前: OpenGL的最新版本为4. ...

随机推荐

  1. NOIP模拟测试9「随·单·题」

    liu_runda出的题,先$\%\%\%\%\%\%\%\%\%\%\%$为敬 随 考试时没有Qj 然后甚至没做,甚至没交 我不知道我怎么想的 这个题挺难改 你需要用到 循环矩阵快速幂,矩阵快速幂优 ...

  2. linux安装配置交叉编译器arm-linux-gnueabi-gcc

    要使我们在x86架构下运行的程序迁移至ARM架构的开发板中运行时,需要通过交叉编译器将x86下编写的程序进行编译后,开发版才能运行. 在安装之前我们需要了解,什么是交叉编译器. 一.下载交叉编译器 这 ...

  3. vue项目使用Echarts制作项目工期甘特图

    目录 1,前言 2,布局和数据部分 3,制作甘特图 1,前言 项目迭代过程中,碰上一个需求,要求用甘特图的方式显示项目的工期进度,开完会我赶紧搜索一下甘特图是啥东东,大概了解之后,做出了如下样式 Ec ...

  4. 玩转STM32MP157- 使用fbtft驱动 lcd ili9341

    之前使用了 fbtft 成功驱动了lcd st7735r,现在尝试下驱动 ili9341, 配置 跟之前用 fbtft 驱动 st7735r 一样,先用 make menuconfig 配置内核,添加 ...

  5. python返回列表最大值(java返回数组最大值)

    b=["3","2","1","6","5","2","1" ...

  6. js jq计算器

    <html><head><meta http-equiv="Content-type" content="text/html; charse ...

  7. 美化terminal时碰到的问题- Set-Theme

    报错: 1 Set-Theme Set-Theme: The term 'Set-Theme' is not recognized as a name of a cmdlet, function, s ...

  8. 8、oracle密码过期设置

    8.1.登录到oracle实例: [oracle@slave-node2 ~]$ echo $ORACLE_SID orcl [oracle@slave-node2 ~]$ sqlplus sys/1 ...

  9. nginx日志分析及其统计PV、UV、IP

    一.nginx日志结构 nginx中access.log 的日志结构: $remote_addr 客户端地址 211.28.65.253 $remote_user 客户端用户名称 -- $time_l ...

  10. Maven中dependencies和dependencyManagement的区别

    Maven项目中,为了保持引用依赖的一致性,一般会抽出一个parent层,用来管理子项目的maven依赖,对于依赖的管理有两种方式,分别是dependencies以及dependencyManagem ...