《OpenCL异构并行编程实战》补充笔记散点,第五至十二章

时间:2023-08-15 22:24:45

▶ 第五章,OpenCL 的并发与执行模型

● 内存对象与上下文相关而不是与设备相关。设备在不同设备之间的移动如下,如果 kernel 在第二个设备上运行,那么在第一个设备上产生的任何数据结果在第二个设备上都是随需可用的。

● OpenCL 中任务图通过事件对象来构建,事件对象不仅注册任务的完成,而且保证被此任务访问的所有内存数据的一致性。

● 一个设备可以有多个命令队列,每个设备都必须有自己的命令队列。

● OpenCL 事件可以实现同一个上下文中的同步,而不能保证同一设备上的同步。

● sub-buffer 对象,允许将单独的一个 bufer 对象拆分为多个可以重叠的较小的 buffer,并使用与原 buffer 相同的方式进行读写。

● image 对象与 buffer 对象主要有几方面不同:image 对象对设备代码来说是不可见的,不能通过直镇进行访问,需要使用特定的函数进行访问;可以使多维结构;仅限于图像相关数据类型,而不能实现任意的数据结构;image 对象始终位于全局内存中。

● image 对象只能制度或只写,不能同时可读可写。

● Z 序(或称 Morton 序)内存布局使用的映射方式保留着数据访问的空间局部性,如下图所示

  《OpenCL异构并行编程实战》补充笔记散点,第五至十二章

● 同一个 work-item 内,对同一个地址的两个读写操作不会被硬件或编译器重新排序

● OpenCL 之定义了整数类型的原子操作,每个有返回值的原子操作将返回操作位置的初始值,若返回值被忽略,编译器见自动改用没有返回值的原子操作

● 本地内存 __local 的申请方法

 cl_uint status = clSetKernelArg(kernel, , sizeof(float), );// 缓冲区地址(最后一个参数)设为 0

▶ 第六章,OpenCL 在 CPU/GPU 上的实现

● OpenCL CPU 运行时将创建一个运行在 CPU 每一个核上的线程,将他作为一个工作池来处理生成的 OpenCL kernel,任务队列的任务分配由核心管理者线程负责,在一个操作系统线程上运行 workgroup,一个 workgroup 内的线程是没有并行性,一次只能处理一个 work-item,在 barrier 或 kernel 的结束处工作线程才对转而处理下一个 work-item

● AMD 的OpenCL CPU 运行时是通过函数 setjmp 和函数 longjmp 来实现每个 barrier 前的任务轮转的

● OpenCL 允许的扩展数据类型和运算举例

 float inputData[] = { .f,.f,.f,.f };
float4 a = inputData[];
float4 b = + (float4)(.f, .f, .f, .f);
float outputData[] = b;

▶ 第七章,数据管理

● 共享内存易购系统芯片(System-on-a-Chip,SoC)

● 讲了缓冲区的几种标识符,不知道是不是翻译的问题,讲的非常烂

 /* cl_mem_flags - bitfield */
#define CL_MEM_READ_WRITE (1 << 0)
#define CL_MEM_WRITE_ONLY (1 << 1)
#define CL_MEM_READ_ONLY (1 << 2)
#define CL_MEM_USE_HOST_PTR (1 << 3)
#define CL_MEM_ALLOC_HOST_PTR (1 << 4)
#define CL_MEM_COPY_HOST_PTR (1 << 5)
/* reserved (1 << 6) */
#define CL_MEM_HOST_WRITE_ONLY (1 << 7)
#define CL_MEM_HOST_READ_ONLY (1 << 8)
#define CL_MEM_HOST_NO_ACCESS (1 << 9)

▶ 第八章,OpenCL 案例学习:卷积

● 容易理解的插图

  《OpenCL异构并行编程实战》补充笔记散点,第五至十二章

● clEnqueueWriteBufferRect 函数,向设备 buffer 中写入较大的二维缓冲区

 extern CL_API_ENTRY cl_int CL_API_CALL clEnqueueWriteBufferRect(
cl_command_queue, // 命令队列
cl_mem, // 目标缓冲区
cl_bool, // 是否阻塞传输
const size_t *, // size_t[3],缓冲区偏移
const size_t *, // size_t[3],主机偏移
const size_t *, // size_t[3],包含数据列宽(Byte),行数,片数
size_t, // 缓冲区数据列宽(Byte)
size_t, // 缓冲区数据片数
size_t, // 主机数据列宽(Byte)
size_t, // 主机数据片数
const void *, // 主机指针
cl_uint, // 事件列表三连
const cl_event *,
cl_event *
) CL_API_SUFFIX__VERSION_1_1;

▶ 第九章,OpenCL 案例学习:直方图

● 为了最有效的利用本地内存,减少到全局内存的数据传输开销,workGroup 的数量应该尽可能接近设备上计算单元或 SIMD 单元的数量

▶ 第十章,OpenCL 案例学习:混合粒子模拟

● 略过代码

▶ 第十一章,OpenCL 扩展

● KHR 扩展,EXT 扩展,厂商扩展

● 某些特性需要有一些编译指导指令来控制.extension_name 是特性名,behavior 为 enable 或 disable

 #pragma OPENCL EXTENSION extension_name : behavior

●  起用双精度浮点数

 #pragma cl_khr_fp64: enable

● 设备拆分(我的电脑不支持)

 #include <iostream>
#include <cl.hpp> #define USE_CL_DEVICE_FISSION 1 using namespace std; int main()
{
vector<cl::Platform>listPlatform;
cl::Platform::get(&listPlatform);
cl_context_properties properties[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)(listPlatform[])(), };// 没有设备的情况下创建上下文
cl::Context context = cl::Context(CL_DEVICE_TYPE_GPU, properties);
vector<cl::Device>listDevice = context.getInfo<CL_CONTEXT_DEVICES>(); if (listDevice[].getInfo<CL_DEVICE_EXTENSIONS>().find("cl_ext_device_fission") == string::npos)
{
cout << "\n\tNot support Device Fission.\n" << endl;
getchar();
exit(-);
} const cl_device_partition_property_ext subDevicePropertities[] = { CL_DEVICE_PARTITION_EQUALLY_EXT, , CL_PROPERTIES_LIST_END_EXT , };
vector<cl::Device>subDevice;
listDevice[].createSubDevices((const cl_device_partition_property *)subDevicePropertities, &subDevice); getchar();
return ;
}

● 略过设备拆封和浮点型矩阵乘法的代码

▶ 第十二章,在其他语言中使用 OpenCL

● JOCL(Java bindings for OpenCL)

● PyOpenCL

● 使用 PyOpenCL 计算两向量和的范例代码:

 import pyopencl as cl
import numpy as np
import numpy.linalg as la a = np.random.rand(50000).astype(np.float32)
b = np.random.rand(50000).astype(np.float32) ctx = cl.create_some_context()
queue = cl.CommandQueue(ctx) mf = cl.mem_flags
a_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf = a)
b_buf = cl.Buffer(ctx, mf.READ_ONLY | mf.COPY_HOST_PTR, hostbuf = b)
c_buf = cl.Buffer(ctx, mf.WRITE_ONLY, b.nbytes) code = """
__kernel void vecadd(__global const float *a, __global const float *b, __global float *c)
{
int gid=get_global_id(0);
c[gid] = a[gid] + b[gid];
}
"""
prg = cl.Program(ctx, code).build() prg.vecadd(queue, a.shape, None, a_buf, b_buf, c_buf) a_plus_b = np.empty_like(a)
cl.enqueue_copy(queue, a_plus_b, c_buf) print(sum(a_plus_b - (a + b)))

● Haskell 语言特点:用表达式描述函数,根据传入的参数进行求值;不定义语句顺序,不允许函数副作用;除了声明以外没有赋值;线程安全的。