OpenCL(壹):了解OpenCL模型到编写第一个CL内核程序

news2024/12/25 20:53:43

目录

        1.前言

        2.简单了解OpenCL

        3.为什么要使用OpenCL

        4.OpenCL架构

        5.OpenCL中的平台模型(Platform Model)

        6.OpenCL中的内存模型(Execution Model)

        7.OpenCL中的执行模型(Memory Model)

        8.OpenCL中的编程模型(Programmin Model)

        9.OpenCL中的同步机制

        10.编写第一个OpenCL程序


前言

        大多数由程序员编写的代码都是执行在CPU上的,而现代计算机不只提供了CPU还提供了GPU,但是大多数场景往往都不会涉及到使用GPU进行运算,有个别业务如ffmpeg编解码,OpenCV中UMat,VTK中的模型渲染等等则会使用到GPU,但是这些都是第三方编写好的函数进行调用。而本系列文章将会对如何把算法迁移到GPU上执行,迁移的主要理由则是作者不满足于压榨语言特性以及编写更高效的算法来使程序获得更高效的执行效率


简单了解OpenCL

        OpenCL全称Open Computing Language即开放计算语言。OpenCL为异构平台提供了一个编写程序,尤其是并行程序的开放的框架标准。OpenCL所支持的异构平台可由多核CPU、GPU或其他类型的处理器组成。OpenCL由两部分组成,一是用于编写内核程序(在OpenCL设备上运行的代码) 的语言,二是定义并控制平台的API。OpenCL提供了基于任务和基于数据两种并行计算机制,它极大地扩展了GPU 的应用范围,使之不再局限于图形领域。


为什么要使用OpenCL

        高性能CPU的由于很难克服提高时钟频率后的散热问题转而使用增加运算核心的方法加速。作为图形渲染专用的处理器, GPU具有高度的并行特性。以下几点是GPU的特点,也是使用GPU运算的理由:

                1.GPU的运算核心数量要远远超过高端 CPU的核心数量。GPU的每个运算核心并没有 CPU的运算核心工作频率高,但是其总体性能-芯片面积比和性能-功耗比都很高,在处理并行计算的相关任务中有很大优势。

                2.GPU是通过大量并行线程之间交织运行隐藏全局访问的延迟,同时GPU还拥有大量的寄存器、局部存储器及Cache等来提升外部存储的访问性能

      而且基于 GPU或者其他并行运算设备的算法与传统的基于CPU的串行算法有很大差别:

                1.并行算法中要有大量的线程在运行,而一般的串行算法都只有一个线程在运行。

                2.并行算法中的每个线程的行为需要尽量保持一致,如果分支很多,各线程又选择不同路径执行,会严重降低 GPU运算的效率。在CPU中,即使有两个线程的行为高度不一致,也不会非常影响性能。

                3.在程序不加特殊约束的情况下,并行运算设备是不保证每个线程看到的全局内存是一致的。程序员有责任维护线程同步以及内存管理等任务。

                4.在传统的串行运算设备中,例如 CPU,线程之间切换的开销是比较大的。所以一般来说,是不 鼓励程序员为一个算法开启大量线程的。而在类似于 GPU的并行运算设备中,线程之间的切换 是非常廉价的。这些设备也正是通过线程之间切换来隐藏一些内存访问延迟的。与CPU相反, 并行设备一般是不鼓励设备运行具有很少线程的算法。


OpenCL架构

        在OpenCL中划分为以下四个模型,这四个模型共同构成了OpenCL框架的核心,使得开发人员能够编写出高效、可移植的并行计算程序:

                1.平台模型(Platform Model)

                2.内存模型(Execution Model)

                3.执行模型(Memory Model)

                4.编程模型(Programmin Model)


OpenCL中的平台模型(Platform Model)

        平台模型主要是由一个主机 (host)连接一个或多个 OpenCL设备构成。其中每个OpenCL设备又可以分割成一个或多个计算单元 (CU),每个计算单元又可以进一步分割成一个或多个处理单元 (PE),各种计算操作都是在处理单元中完成的。所有由 OpenCL编写的应用程序都是从 host启动并在 host上结束的,host端管理着整个平台上的所有计算资源。应用程序会从host端向OpenCL设备的处理单元发送计算命令。

       |-----OpenCL设备
host----     
       |-----OpenCL设备--------计算单元CU
                          |
                          |----计算单元CU-----------处理单元PE
                                              |
                                              |----处理单元PE

99c6400d04064248b143ffcdaa1ce609.png

图1.OpenCL中的平台模型架构


OpenCL中的内存模型(Execution Model)

        内存模型主要分为以下四种:

                1.全局内存:对所有工作组可见,主要存储数据和回传至host的数据

                2.局部内存:只对同一个工作组内的工作节点可见,映射到片上物理内存,具有较短的访问延迟和较高的传输带宽

                3.私有内存:只能由工作节点自己访问,局部变量和非指针内核传输通常都在私有内存是开辟存储空间

                4.常量内存:存储只读数据,所有工作节点都可以访问,一个OpenCL设备对应一个全局内存,而在全局内存中又划分了一部分地址作为常量内存

       |-----全局内存
host----     
       |-----全局内存--------工作组
                       |
                       |----工作组-----------工作节点
                                       |
                                       |----工作节点

 

 

全局内存

常量内存

本地内存

私有内存

Host

分配

动态

动态

动态

不可分配

访问

可读写

可读写

不可访问

不可访问

Kerenl

分配

不可访问

静态

静态

静态

访问

可读写

只读

可读写

可读写

表1.host和kernel是如何管理和使用各类内存

        而由于OpenCL设备和host之间的内存是互相独立的,所有host要与OpenCL设备进行数据的交互通常通过以下两种方式(这两种方式都分为non-block模式和block模式):

                1.拷贝数据法:host通过相应的OpenCL的API接口将数据从host写入到OpenCL设备的内存中或者从OpenCL设备内存读出数据到host内存中

                2.内存映射法:通过相应OpenCL的API接口将OpenCL的内存对象映射到 host端可见的内存地址空间中。映射之后用户就可以在host端的映射地址读写该内存了,在读写完成之后用户必须使用对应 API解除这种映射关系

e18d70e5bedf452eb797473ec085717c.png

图2.OpenCL中的内存模型架构


OpenCL中的执行模型(Memory Model)

        OpenCL中的执行模型主要用于管理kernel在OpenCL设备上的运行,它分为以下两种:

                1.host上执行的主程序

                2.OpenCL设备上执行的内核程序Kernel

        对于Kernel程序的工作空间又划分为工作组空间,在Host创建Kernel程序时,必须先为该Kernel程序创建工作空间,该空间可以是一维(线),也可以是二维(矩阵)和三维(立体)。所有工作节点的执行同一个Kernel程序,而每一个节点在相应维度上的索引被定义为该节点在该维度上的全局ID。工作组的空间必须与Kernel程序的工作空间维度相同,整个工作空间可以划分为多个工作组空间,每一个工作组都有对应的工作组空间(这里其实就相当于内存模型中的全局内存内含多个工作组(局部内存),局部内存中又有多个工作节点(私有内存))

        对应的上段讲到的全局ID是工作节点在Kernel工作空间相应维度的索引(简单理解为相对于全局内存的索引),局部ID则是工作节点对于该工作组空间的索引(相对于局部内存的索引)

42ff3ea941624b04b40e4a1f7630f38c.png

图3.OpenCL中的执行模型架构


OpenCL中的编程模型(Programmin Model)

        OpenCL中的编程模型主要分为以下两种:

                1.数据并行模型:运行相同的内核程序,根据全局ID或者局部ID映射的内存导致处理的数据不同,而数据并行模型又分为以下两种:

                        1.显式分级模型:指定参与并行计算的工作节点的数目和工作节点所属的工作组

                        2.隐式分级模型:工作节点和工作组由OpenCL分配管理

                2.任务并行模型:每个工作节点在执行内核程序时是独立的,但是工作节点间的数据可以通过全局内存或局部内存进行数据交互

        为了方便理解后续的内容,此次对Kernel程序和Native Kernel程序进行补充:

                1.Kernel程序指的是内核程序,运行在OpenCL设备中的程序

                2.Native Kernel程序指的是运行在Host上的程序

        在OpenCL中实习任务并行模型的方法主要是通过使用OpenCL设备支持的向量类型数据结构,同时执行或者选择性执行多个Kernel程序和执行Kernel程序的同时交叉执行Native Kernel程序


OpenCL中的同步机制

        OpenCL中的同步主要分为两个概念,一个是同步领域,一个是同步方法。

        同步领域:

                1.工作组内同步:实现同一个工作组中所有工作节点之间的同步

                2.命令队列间同步:实现同一个上下文中不同的命令队列之间和同一个命令队列的不同命令之间的同步

        同步方法:

                1.工作组内通过阻断函数实现

                2.命令队列内同步通过OpenCL提供的命令队列的阻断函数

                3.不同命令队列间同步通过每一个命令关联的事件实现同步


编写第一个OpenCL程序

        在编写第一个OpenCL程序前我们需要先了解OpenCL程序的基本流程:

d814be55eec64ccabe6091ab732bc77d.png

图4.OpenCL程序基本流程

#include <CL/cl.h>
#include <iostream>

#define KERNEL(...)#__VA_ARGS__		//使用#__VA_ARGS_宏将传入KERNEL宏的实参转为字符串

//编写内核函数,该内核函数将转换为字符串并由指针kernelSourseCode指向
const char* kernelSourceCode = KERNEL(
	__kernel void wildpointer(__global uint * buffer) {
		size_t gidx = get_global_id(0);
		size_t gidy = get_global_id(1);
		size_t lidx = get_local_id(0);
		buffer[gidx + 4 * gidy] = (1 << gidx) | (0x10 << gidy);
	}
);

int main() {
	std::cout << "WildPointer:Hello OpenCL" << std::endl;
	//获取可用的设备平台ID
	cl_int status = 0;
	size_t deviceListSize;
	cl_uint numPlatforms;
	cl_platform_id platform = NULL;
	status = clGetPlatformIDs(0, NULL, &numPlatforms);
	if (status != CL_SUCCESS) {
		std::cout << "错误:获取设备失败" << std::endl;
		return EXIT_FAILURE;
	}
	if (numPlatforms > 0) {
		cl_platform_id* platforms = (cl_platform_id*)malloc(numPlatforms * sizeof(cl_platform_id));
		status = clGetPlatformIDs(numPlatforms, platforms, NULL);
		if (status != CL_SUCCESS) {
			std::cout << "错误:获取平台ID失败" << std::endl;
			return -1;
		}
		for (unsigned int i = 0; i < numPlatforms; ++i) {
			char pbuff[100];
			status = clGetPlatformInfo(platforms[i], CL_PLATFORM_VENDOR, sizeof(pbuff), pbuff, NULL);
			platform = platforms[i];
			if (!strcmp(pbuff, "GPU")) {
				break;
			}
		}
		delete platforms;
	}

	//找到可用的设备平台
	cl_context_properties cps[3] = {CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };
	cl_context_properties* cprops = (NULL == platform) ? NULL : cps;

	// 使用正确的设备类型创建上下文
	cl_context context = clCreateContextFromType(cprops, CL_DEVICE_TYPE_GPU, NULL, NULL, &status);
	if (status != CL_SUCCESS) {
		std::cout << "错误:为GPU生成上下文失败" << std::endl;
		return EXIT_FAILURE;
	}

	//寻找OpenCL设备(GPU)
	//获取设备个数
	status = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceListSize);
	if (status != CL_SUCCESS) {
		std::cout << "错误:寻找可用的设备个数失败" << std::endl;
		return EXIT_FAILURE;
	}
	cl_device_id* devices = (cl_device_id*)malloc(deviceListSize);
	if (devices == 0) {
		std::cout << "错误:当前可用设备数为0" << std::endl;
		return EXIT_FAILURE;
	}

	//获取设备列表
	status = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceListSize, devices, NULL);
	if (status != CL_SUCCESS) {
		std::cout << "错误:获取设备信息失败" << std::endl;
		return EXIT_FAILURE;
	}

	//装载内核程序,编译CL程序,生成OpenCL内核实例
	size_t sourceSize[] = { strlen(kernelSourceCode) };
	cl_program program = clCreateProgramWithSource(context, 1, &kernelSourceCode, sourceSize, &status);
	if (status != CL_SUCCESS) {
		std::cout << "错误:将二进制文件装载到内核程序失败" << std::endl;
		return EXIT_FAILURE;
	}

	//为指定的OpenCL设备生成CL程序
	status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
	if (status != CL_SUCCESS) {
		std::cout << "错误:编译CL程序失败" << std::endl;
		return EXIT_FAILURE;
	}

	//获取内核实例的句柄
	cl_kernel kernel = clCreateKernel(program, "wildpointer", &status);
	if (status != CL_SUCCESS) {
		std::cout << "错误:在程序上初始化内核程序失败" << std::endl;
		return EXIT_FAILURE;
	}

	//创建OpenCL命令队列
	cl_command_queue commandQueue = clCreateCommandQueue(context, devices[0], 0, &status);
	if (status != CL_SUCCESS) {
		std::cout << "错误:初始化命令队列失败" << std::endl;
		return EXIT_FAILURE;
	}

	//创建OpenCL缓冲区
	unsigned int* outbuffer = new unsigned int[4 * 4];
	memset(outbuffer, 0, 4 * 4 * 4);
	cl_mem outputBuffer = clCreateBuffer(context, CL_MEM_ALLOC_HOST_PTR, 4 * 4 * 4, NULL, &status);
	if (status != CL_SUCCESS) {
		std::cout << "错误:初始化OpenCL缓冲区失败" << std::endl;
		return EXIT_FAILURE;
	}

	//将参数传入内核程序
	status = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&outputBuffer);
	if (status != CL_SUCCESS) {
		std::cout << "错误:设置内核参数失败" << std::endl;
		return EXIT_FAILURE;
	}

	//将内核程序插入命令队列
	size_t globalThreads[] = { 4, 4 };
	size_t localThread[] = { 2, 2 };
	status = clEnqueueNDRangeKernel(commandQueue, kernel, 2, NULL, globalThreads, localThread, 0, NULL, NULL);
	if (status != CL_SUCCESS) {
		std::cout << "错误:内核程序插入命令队列失败" << std::endl;
		return EXIT_FAILURE;
	}

	status = clFinish(commandQueue);
	if (status != CL_SUCCESS) {
		std::cout << "错误:完全命令队列" << std::endl;
		return EXIT_FAILURE;
	}

	status = clEnqueueReadBuffer(commandQueue, outputBuffer, CL_TRUE, 0, 4 * 4 * 4, outbuffer, 0, NULL, NULL);
	if (status != CL_SUCCESS) {
		std::cout << "错误:读取缓冲区队列失败" << std::endl;
		return EXIT_FAILURE;
	}

	std::cout << "内核程序输出:" << std::endl;
	for (int i = 0; i < 16; ++i) {
		std::cout << outbuffer[i] << " ";
		if ((i + 1) % 4 == 0) {
			std::cout << std::endl;
		}
	}

	status = clReleaseKernel(kernel);
	status = clReleaseProgram(program);
	status = clReleaseMemObject(outputBuffer);
	status = clReleaseCommandQueue(commandQueue);
	status = clReleaseContext(context);
	free(devices);
	delete outbuffer;
	return 0;
}

PS:针对代码调用的函数会在下一章讲解,由于内容过长就不放到同一篇文章中了,具体如果了解了OpenCL中的四大模型,并且熟悉C/C++语言则对于阅读上述程序不会太吃力

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2265466.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter组件————Scaffold

Scaffold Scaffold 是一个基础的可视化界面结构组件&#xff0c;它实现了基本的Material Design布局结构。使用 Scaffold 可以快速地搭建起包含应用栏&#xff08;AppBar&#xff09;、内容区域&#xff08;body&#xff09;、抽屉菜单&#xff08;Drawer&#xff09;、底部导…

【数据结构】数据结构整体大纲

数据结构用来干什么的&#xff1f;很简单&#xff0c;存数据用的。 &#xff08;这篇文章仅介绍数据结构的大纲&#xff0c;详细讲解放在后面的每一个章节中&#xff0c;逐个击破&#xff09; 那为什么不直接使用数组、集合来存储呢 ——> 如果有成千上亿条数据呢&#xff…

搭建Elastic search群集

一、实验环境 二、实验步骤 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎Elasticsearch目录文件&#xff1a; /etc/elasticsearch/elasticsearch.yml#配置文件 /etc/elasticsearch/jvm.options#java虚拟机 /etc/init.d/elasticsearch#服务启动脚本 /e…

链原生 Web3 AI 网络 Chainbase 推出 AVS 主网, 拓展 EigenLayer AVS 场景

在 12 月 4 日&#xff0c;链原生的 Web3 AI 数据网络 Chainbase 正式启动了 Chainbase AVS 主网&#xff0c;同时发布了首批 20 个 AVS 节点运营商名单。Chainbase AVS 是 EigenLayer AVS 中首个以数据智能为应用导向的主网 AVS&#xff0c;其采用四层网络架构&#xff0c;其中…

玩转OCR | 探索腾讯云智能结构化识别新境界

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ 玩转OCR 腾讯云智能结构化识别产品介绍服务应用产品特征行业案例总结 腾讯云智能结构化识别 腾讯云智能结构化OCR产品分为基础版与高级版&am…

生信软件开发2 - 使用PyQt5开发一个简易GUI程序

往期文章&#xff1a; 生信软件开发1 - 设计一个简单的Windwos风格的GUI报告软件 1. 使用PyQt5设计一个计算器主程序 要求PyQt5 > 5.6, calculator.py与MainWindow.py处于同一目录&#xff0c;下载mainwindow-weird.ui和mainwindow.ui资源&#xff0c;运行calculator.py即…

“计算几何”简介

计算几何&#xff08;Computational Geometry&#xff09;简单来说就是用计算机解决几何问题。 Computational指“using or connected with computers使用计算机的&#xff1b;与计算机有关的”&#xff0c;Geometry指“the branch of mathematics that deals with the measur…

TowardsDataScience 博客中文翻译 2018~2024(一百二十三)

TowardsDataScience 博客中文翻译 2018~2024&#xff08;一百二十三&#xff09; 引言 从 2018 年到 2024 年&#xff0c;数据科学的进展超越了许多技术领域的速度。Towards Data Science 博客依然是这个领域的关键平台&#xff0c;记录了从基础工具到前沿技术的多方面发展。…

GitHub 桌面版配置 |可视化界面进行上传到远程仓库 | gitLab 配置【把密码存在本地服务器】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 桌面版安装包下载clone 仓库操作如下GitLab 配置不再重复输入账户和密码的两个方…

今天最新早上好问候语精选大全,每天问候,相互牵挂,彼此祝福

1、朋友相伴&#xff0c;友谊真诚永不变&#xff01;彼此扶持绿树荫&#xff0c;共度快乐雨后天&#xff01;一同分享的表情&#xff0c;愿我们友情长存&#xff0c;一生相伴永相连&#xff01; 2、人生几十年&#xff0c;苦累伴酸甜&#xff0c;风华不再茂&#xff0c;雄心非当…

Verdi -- 打开Consol,创建和执行tcl命令举例

1.Verdi打开Console的步骤&#xff1a; For ref: 2创建tcl脚本. tcl脚本路径&#xff1a; 在Makefile下&#xff0c;与.v文件在同一个目录8_demo这个文件夹下。 font.tcl代码内容&#xff1a; verdiSetFont -monoFont "Courier" -monoFontSize "24" 作用…

基于java博网即时通讯软件的设计与实现【源码+文档+部署讲解】

目 录 1. 绪 论 1.1. 开发背景 1.2. 开发意义 2. 系统设计相关技术 2.1 Java语言 2.2 MySQL数据库 2.3 Socket 3. 系统需求分析 3.1 可行性分析 3.2 需求分析 3.3 系统流程图 3.4 非功能性需求 4. 系统设计 4.1 系统功能结构 4.2 数据库设计 5. 系统实现 5.…

视频汇聚融合云平台Liveweb一站式解决视频资源管理痛点

随着5G技术的广泛应用&#xff0c;各领域都在通信技术加持下通过海量终端设备收集了大量视频、图像等物联网数据&#xff0c;并通过人工智能、大数据、视频监控等技术方式来让我们的世界更安全、更高效。然而&#xff0c;随着数字化建设和生产经营管理活动的长期开展&#xff0…

Hadoop集群(HDFS集群、YARN集群、MapReduce​计算框架)

一、 简介 Hadoop主要在分布式环境下集群机器&#xff0c;获取海量数据的处理能力&#xff0c;实现分布式集群下的大数据存储和计算。 其中三大核心组件: HDFS存储分布式文件存储、YARN分布式资源管理、MapReduce分布式计算。 二、工作原理 2.1 HDFS集群 Web访问地址&…

文本的AIGC率检测原理

背景 你可能在学生群里或者视频中看过这样的消息&#xff1a;“我们学校要求论文AI率不能超过30%&#xff01;”、“你们学校查AI率吗&#xff1f;”之类的&#xff0c;这些消息到底是真是假&#xff1f; 随着人工智能的快速发展和广泛应用&#xff0c;不论是工作中还是学生学…

PODS:2024-12-21由麻省理工学院 和 OpenAI联合创建一个专门为个性化对象识别任务设计的数据集.

2024-12-21&#xff0c;由MIT和OpenAI联合创建的个性化视觉数据集&#xff0c;为细粒度和数据稀缺的个性化视觉任务提供了新的解决方案&#xff0c;推动了个性化模型的发展&#xff0c;具有重要的研究和应用价值。 一、研究背景&#xff1a; 在计算机视觉领域&#xff0c;现代…

OpenFeign快速入门 示例:黑马商城

使用起因 之前我们利用了Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。这样一来购物车虽然通过远程调用实现了调用商品服务的方法,但是远程调用的代码太复杂了: 解决方法 并且这种调用方式比较复杂&#xff0c;一会儿远程调用&#xff0c;一会儿本地调用。 因…

YOLOv11模型改进-模块-引入多尺度大核注意力Multi-scale Large Kernel Attention

MLKA 的提出源于图像超分辨率任务的挑战性&#xff0c;该任务需重建低质量图像缺失的高频信息&#xff0c;但因 LR 与 HR 图像对应关系复杂&#xff0c;寻找像素相关性困难。此前模型扩展容量的方法增加了训练负担和数据收集成本&#xff0c;而采用的注意力机制无法同时获取局部…

学习思考:一日三问(学习篇)之匹配VLAN

学习思考&#xff1a;一日三问&#xff08;学习篇&#xff09;之匹配VLAN 一、学了什么&#xff08;是什么&#xff09;1.1 理解LAN与"V"的LAN1.2 理解"V"的LAN怎么还原成LAN1.3 理解二层交换机眼中的"V"的LAN 二、为何会产生需求&#xff08;为…

国际网络专线怎么申请开通?

随着国内企业在国际市场中的活跃度逐年提升&#xff0c;国际网络专线逐渐成为保障企业高效运营的重要基础设施。稳定且高效的网络不仅能够提升工作效率&#xff0c;还能为海外业务的顺利开展提供可靠保障。那么&#xff0c;国际网络专线如何开通&#xff1f;其申请流程是怎样的…