OpenCL™ 主机代码基本示例

Download PDF [686.3 kB]

Download Sample OCL ZIP [10.89 mB]

目录

  1. 介绍
  2. 示例介绍
  3. OpenCL 实施。
  4. 限制
  5. OpenCL 应用基本原理。
  6. 项目结构。
  7. 所使用的 OpenCL API。
  8. 控制示例。
  9. 参考文献。

介绍

新接触 OpenCL 的编程人员可能会发现,最完整的文档 Khronos OpenCL 规范并不适合作为 OpenCL 编程的开始。 该规格介绍了许多选项和备选方案,最初可能会让编程人员晕头转向。   其他面向 OpenCL 编写的代码示例可能主要使用设备内核代码,或使用用 OpenCL “包装器”库编写的主机代码,这会隐藏如何直接使用标准 OpenCL 主机 API 的具体细节。  

本文中介绍的 SampleOCL 旨在呈现清晰、可读的复杂(non-trivial)OpenCL 程序的基本元素。 该示例代码是面向主机(CPU)而非内核代码或性能 OpenCL™ 代码。  它展示了如何使用 OpenCL v1.2 规范构建一个简单的 OpenCL 应用的基本原理。[1] 同样,本文主要介绍了主机代码的结构以及该代码使用的 OpenCL API。

示例介绍

该代码示例使用的 OpenCL 内核与 ToneMapping 示例相同(参见下文参考文献),后者曾在面向 OpenCL 应用的英特尔® SDK 中发布[2]。 该简单内核旨在帮助由于太暗或太亮而分辨不清的图片清晰可见。 它从输入缓冲区中读取像素,对其进行修改,然后将其编写至输出缓冲区的同一位置。 关于该内核如何运行的更多信息,请参见文档高动态范围色调映射后期处理效果(High Dynamic Range Tone Mapping Post Processing Effect )[3]。

OpenCL 实施

SampleOCL 示例应用并不是要“包装” OpenCL,也就是说,它并非要使用“更高级的” API 来替换 OpenCL API。 总体而言,我发现,这些包装器不比直接使用 OpenCL API 更简单或更整洁,而且,虽然最初创建包装器的编程人员认为其使用起来更简单,但是包装器会对维护代码的 OpenCL 编程人员带来很大负担。 OpenCL API 是一个标准。 如要在专门的“改进” API 中对其进行包装,则需要丢弃许多使用该标准时的值。

依次说法,SampleOCL 实施的确使用了一些 C++ 类及相关方法将 OpenCL API 分成了几组。 该应用主要分为两大类,以区分通用应用元素和与 OpenCL 相关的元素。 前者是 C_SampleOCLApp,后者是 C_OCL。

限制

该示例代码仅关注 OpenCL 应用的基本原理,如版本 1.2 中的说明。 它并没有介绍与其他修订版之间的不同,但是其大部分信息应与最新修订版相关。

该示例的主机端应用代码并非要展示最优性能。 简便起见,我们将多个明显的优化略去。

OpenCL 应用基本原理

接下来,我们将对基本 OpenCL 应用程序序列进行完整的介绍。 我们在此强调“基本”,因为有许多选项并未涉及。 更多信息请参见 OpenCL 规范[1]。

OpenCL 应用需要能够在多处理设备上大规模并行,如采用 SIMD 指令和图形处理单元(GPU)的多核 CPU — 无论是独立还是集成至 CPU。 因此,OpenCL 应用首先需要能够确定哪些设备可用,并选择一台或多台设备进行使用。 一个平台可能支持多种设备,如包括集成 GPU 的 CPU,而且应用能够使用多个平台。

OpenCL 应用的每个可用平台都包括相关的名称、厂商等。该信息可通过使用 OpenCL API clGetPlatformIDs(),然后再使用 clGetPlatformInfo() 来获取,而且可用于选择目标平台。

选定平台后,必须创建一个环境(context)来实施应用所需的 OpenCL 设备、内存和其他资源。 拥有所选平台 ID 和目标设备类型(CPU、GPU 等)的规范后,应用便能够调用 clCreateContextFromType(),然后使用 clGetContextInfo() 来获取设备 ID。 或者,它可以直接使用 clGetDeviceIDs() 在给定平台 ID 上请求设备 ID,然后使用带有这些设备 ID 的 clCreateContext() 创建环境。 本示例使用了第二种方法来创建带有一个 GPU 设备的环境。

拥有目标设备 ID 和环境后,我们便可使用 clCreateCommandQueue() 为每台要使用的设备创建命令队列。 命令队列用于主机应用至 GPU 或其他设备的“排队”操作,例如,申请执行某个 OpenCL 内核。 本示例代码为 GPU 设备创建了一个命令队列。

初始化操作完成后,通常我们接下来将会使用 clCreateProgramWithSource() 创建一个或多个 OpenCL 程序对象。 程序创建后,它还需要使用 clBuildProgram() 进行构建(基本的编写和链接)。 该 API 支持为编译器设置选项,如设置 #defines 以修改程序源代码。

最后,借助创建和构建的程序,我们可创建链接至该程序中的函数的内核对象,从而为每个内核函数名称调用 clCreateKernel()。

运行 OpenCL 内核前,需要先设置要处理的数据,通常通过使用 clCreateBuffer() API 函数创建线性内存缓冲区来完成。 (本示例中未使用映像作 为 OpenCL 内存对象类型。) clCreateBuffer 函数可为既定尺寸的缓冲区分配内存,并可随意从主机内存复制数据,而且,它能够设置缓冲区,以直接使用主机代码已经分配的空间。 (后者能够避免从主机内存复制至 OpenCL 缓冲区,这是常见的性能优化。)

一般而言,内存将至少使用一个输入和一个输出缓冲区以及其他参数。 每次只能设置一个参数,以便内核在执行时访问。访问时只需调用每个参数的 clSetKernelArg() 函数即可。 该函数可使用内核函数参数列表中的一个特殊参数 — 数字索引进行调用。 第一个参数使用 index 0 传递,第二个参数 index 1 等。

借助参数集,调用包含内核对象和命令队列的函数 clEnqueueNDRangeKernel(),申请运行该内核。 内核排队后,主机代码可以做其他的事情,或者可以通过调用 clFinish() 函数等待内核(及之前加入队列的所有任务)完成。 本示例可以调用 clFinish(),因为它包括能够记录一个循环中总内核执行(包括所有排队开销)的时间,该循环需要等待所有执行都完成后, 才能记录最终时间或得出平均时间。

以上是构建 OpenCL 应用的部分。 此外,还有一些清除操作,如调用 clReleaseKernel、clReleaseMemObject、clReleaseProgram 等。虽然 OpenCL 在程序退出时应自动释放所有资源,但是本示例中仍包括这些操作。 较为复杂的程序可能希望即时释放资源以避免内存泄露。

最后应注意的一点是:虽然本示例没有使用“事件”,但是它们对于(比如)希望覆盖 CPU 和 GPU 处理的复杂应用非常有用。 但是,应注意到,任何将指针传递至事件的 clEnqueueXXXXX() 函数(其中 “XXXXX” 用众多可行函数中一个的名称进行替换)都将分配一个事件,然后调用代码负责在某一时刻将包含指针的 clReleaseEvent() 调用至事件。 如果该操作未完成,随着事件累积,程序将会出现内存泄露。

常见的错误是使用 clCreateUserEvent() 函数分配事件以传递至 clEnqueueXXXX 函数,这种操作认为完成后 OpenCL 将会标记该事件。 OpenCL 不会使用该事件,clEnqueueXXXX 将会返回新事件,覆盖指针传递的事件变量的内容。 这种方式很容易导致内存漏洞。 除了本示例的范围以外,用户事件还有其他目的。 关于 OpenCL 事件的更多详细信息,请参见 OpenCL 规范。[1]

项目结构

_tmain ( argc, argv ) - Main.cpp 文件中的主要接入点函数。

创建 C_SampleOCLApp 类的实例。

调用 C_SampleOCLApp::Run() 以启动应用。

这是它的全部功能! 参见以下 C_MainApp 和 C_SampleApp 类了解更多信息。

C_MainApp 类 - C_MainApp.h 文件中的通用 OpenCL 应用超类(super-class)

构建时,创建 OpenCL 类 C_OCL 的实例。

 

定义通用应用 “run” 函数:

Run()

Run() 是读取代码以理解 OpenCL 应用如何初始化、运行和清理的良好起点。

Run() 可调用具有代表性的简单应用序列中的虚拟函数(见下文)。

 

声明要由 C_SampleOCLApp 定义的虚拟函数(见下文):

AppParseArgs ()

解析命令行选项

AppUsage ()

打印使用说明

AppSetup ()

应用设置,包括 OpenCL 设置

AppRun ()

特定应用操作

AppCleanup ()

应用清除

 

C_SampleOCLApp 类 - 来自 C_MainApp,可专门针对本示例定义函数。

SampleApp.cpp SampleApp.h 文件中的 C_MainApp 虚拟函数实施应用特定代码。 (参见 C_MainApp 类(见上文),了解实施的虚拟函数。)

ToneMap_OCL.cpp 文件中定义 "ToneMap" OpenCL 内核设置和运行函数:

RunOclToneMap ()

为 ToneMap 执行一次性设置,然后调用 ToneMap()。

ToneMap ()

设置 ToneMapping 内核参数并运行该内核。

 

C_OCL 类 - 大部分的主机端 OpenCL API 可设置并清理代码。

构建时,初始化 OpenCL。 销毁时,在 OpenCL 之后清除。

C_OCL.cpp C_OCL.h 文件中定义 OpenCL 服务函数:

Start ()

为适当平台的英特尔® 锐炬™ 显卡设置 OpenCL 设备。

ReadAllPlatforms ()

获取所有可用 OpenCL 平台,保存其名称。

MatchPlatformName ()

辅助函数,可通过名称来选择平台。

GetDeviceType ()

辅助函数,可确定设备类型是 GPU 还是 CPU。

CheckExtension ()

检查某个 OpenCL 扩展名是否能够在目前的设备上使用。

ReadExtensions ()

获取列出当前设备的所有 OpenCL 扩展名的字符串。

SetCurrentDeviceType ()

设置目标设备类型并创建 OpenCL 环境和命令队列。

CreateProgramFromFile ()

加载包含 OpenCL 内核的文件,创建 OpenCL 程序并对其进行构建。

ReadSourceFile ()

将 OpenCL 内核源文件读入字符串,准备将其构建为程序。

CreateKernelFromProgram ( )

从以前构建的程序中创建 OpenCL 内核。

GetDeviceInfo ()

获取设备特定信息的两个辅助函数:其一可分配内存以接收和返回结果;其二可通过指针将结果返回至调用程序提供的内存。

ClearAllPlatforms ()

释放与以前选中的平台相关的所有内容。

ClearAllPrograms ()

释放所有现有 OpenCL 程序。

ClearAllKernels ()

释放所有现有 OpenCL 内核。

 

所使用的 OpenCL API

clBuildProgram

clCreateBuffer

clCreateCommandQueue

clCreateContext

clCreateKernel

clCreateProgramWithSource

clEnqueueMapBuffer

clEnqueueNDRangeKernel

clEnqueueUnmapMemObject

clFinish

clGetDeviceIDs

clGetDeviceInfo

clGetPlatformIDs

clGetPlatformInfo

clReleaseCommandQueue

clReleaseContext

clReleaseDevice

clReleaseDevice

clReleaseKernel

clReleaseMemObject

clReleaseProgram

clSetKernelArg

控制示例

本示例从 Microsoft Windows* 命令行控制台运行。 它支持以下命令行和可选参数:

ToneMapping.exe [ ? | --h ] [-c|-g] [-list] [-p "platformName] [-i "full image filename"]

? OR --h

打印该帮助消息

-c

在 CPU 上运行 OpenCL

-g

在 GPU 上运行 OpenCL — 默认

-list

显示平台名称字符串列表

-p "platformName"

提供平台名称(如果有空间将会用引号标出)以供检查和使用。

-i "full image filename"

提供图像文件名称(如果有空间将会用引号标出)以供处理。

参考文献

  1. OpenCL Specifications from Khronos.org:

    http://www.khronos.org/registry/cl/

  2. Intel® SDK for OpenCL™ Applications: http://software.intel.com/en-us/vcsource/tools/opencl-sdk
  3. High Dynamic Range Tone Mapping Post Processing Effect:

    http://software.intel.com/en-us/vcsource/samples/hdr-tone-mapping

 

英特尔、Intel 标识、 Iris 和锐炬是英特尔在美国和其他国家的商标。
* 其他的名称和品牌可能是其他所有者的资产。
OpenCL 和 OpenCL 标识是苹果公司的商标,需获得 Khronos 的许可方能使用。
英特尔公司 © 2014 年版权所有。 所有权保留。

有关编译器优化的更完整信息,请参阅优化通知