OpenCL

OpenCL（，中译：开放计算语言），是一个为异构平台编写程序的框架，此异构平台可由CPU、GPU、DSP、FPGA或其他类型的处理器与硬体加速器所组成。OpenCL由一门用于编写kernels（在OpenCL设备上运行的函数）的语言（基于C99）和一组用于定义并控制平台的API组成。OpenCL提供了基于任务分割和数据分割的并行计算机制。
OpenCL类似于另外两个开放的工业标准OpenGL和OpenAL，这两个标准分别用于三维图形和计算机音频方面。OpenCL扩充了GPU图形生成之外的能力。OpenCL由非盈利性技术组织Khronos Group掌管。
历史.
OpenCL最初由苹果公司开发，拥有其商标权，并在与AMD，IBM，Intel和NVIDIA技术团队的合作之下初步完善。随后，苹果将这一草案提交至Khronos Group。2008年6月16日，Khronos的通用计算工作小组成立。5个月后的2008年11月18日，该工作组完成了OpenCL 1.0规范的技术细节。该技术规范在由Khronos成员进行审查之后，于2008年12月8日公开发表。
2010年6月14日，OpenCL 1.1发布。2011年11月15日，OpenCL 1.2发布。2013年11月18日，OpenCL 2.0发布。2015年11月16日，OpenCL 2.1发布。2017年5月16日，OpenCL 2.2发布。
路线图.
在2017年5月发行OpenCL 2.2之时，Khronos Group宣布OpenCL将尽可能的汇合于Vulkan，以确使OpenCL软件在这两种API上灵活部署。这已经由Adobe的Premiere Rush展示出来，它使用clspv开源编译器，编译了大量OpenCL C内核代码，使其在部署于Android的Vulkan运行时系统上运行。
OpenCL拥有独立于Vulkan的前瞻性路线图，即曾意图在2020年发行的“OpenCL Next”，它可以集成于扩展诸如Vulkan/OpenCL互操作、Scratch-Pad内存管理、扩展子组、SPIR-V 1.4摄入和SPIR-V扩展调试信息；OpenCL还在考虑类似Vulkan的装载器和分层以及“灵活配置”，以便在多种加速类型上灵活部署。
OpenCL 3.0.
在2020年8月30日，发行了最终的OpenCL 3.0规范。OpenCL 1.2功能已经成为强制性基准，而所有OpenCL 2.x和OpenCL 3.0特征变为可选项。这个规范保留了“OpenCL C”语言，并废弃了版本2.1介入的“OpenCL C++”内核语言，将其替代为“C++ for OpenCL”语言，它基于了Clang/LLVM编译器，实现了C++17的子集和中间代码。C++ for OpenCL版本1.0的官方文档在2020年12月发表，它后向兼容于OpenCL C 2.0。
在 21上发布的OpenCL 3.0.7，提出了C++ for OpenCL的新版本和一些Khronos openCL扩展。在2021年12月，发行了C++ for OpenCL版本2021，它完全兼容于OpenCL 3.0标准。NVIDIA密切协作于Khronos OpenCL工作组，通过信号量和内存共享改进了Vulkan互操作。小更新3.0.14版本，具有缺陷修正和针对多设备的一个新扩展。
范例.
快速傅立叶变换.
一个快速傅立叶变换的式子：
 // create a compute context with GPU device
 context = clCreateContextFromType(NULL, CL_DEVICE_TYPE_GPU, NULL, NULL, NULL);
 // create a command queue
 queue = clCreateCommandQueue(context, NULL, 0, NULL);
 // allocate the buffer memory objects
 memobjs[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(float)*2*num_entries, srcA, NULL);
 memobjs[1] = clCreateBuffer(context, CL_MEM_READ_WRITE, sizeof(float)*2*num_entries, NULL, NULL);
 // create the compute program
 program = clCreateProgramWithSource(context, 1, &amp;fft1D_1024_kernel_src, NULL, NULL);
 // build the compute program executable
 clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
 // create the compute kernel
 kernel = clCreateKernel(program, "fft1D_1024", NULL);
 // set the args values
 clSetKernelArg(kernel, 0, sizeof(cl_mem),(void *)&amp;memobjs[0]);
 clSetKernelArg(kernel, 1, sizeof(cl_mem),(void *)&amp;memobjs[1]);
 clSetKernelArg(kernel, 2, sizeof(float)*(local_work_size[0]+1)*16, NULL);
 clSetKernelArg(kernel, 3, sizeof(float)*(local_work_size[0]+1)*16, NULL);
 // create N-D range object with work-item dimensions and execute kernel
 global_work_size[0] = num_entries;
 local_work_size[0] = 64;
 clEnqueueNDRangeKernel(queue, kernel, 1, NULL, global_work_size, local_work_size, 0, NULL, NULL);
真正的运算：（基于Fitting FFT onto the G80 Architecture）
 // This kernel computes FFT of length 1024. The 1024 length FFT is decomposed into
 // calls to a radix 16 function, another radix 16 function and then a radix 4 function
 __kernel void fft1D_1024(__global float2 *in, __global float2 *out,
 __local float *sMemx, __local float *sMemy){
 int tid = get_local_id(0);
 int blockIdx = get_group_id(0) * 1024 + tid;
 float2 data[16];
 // starting index of data to/from global memory
 in = in + blockIdx; out = out + blockIdx;
 globalLoads(data, in, 64); // coalesced global reads
 fftRadix16Pass(data); // in-place radix-16 pass
 twiddleFactorMul(data, tid, 1024, 0);
 // local shuffle using local memory
 localShuffle(data, sMemx, sMemy, tid, (((tid &amp; 15)* 65) +(tid » 4)));
 fftRadix16Pass(data); // in-place radix-16 pass
 twiddleFactorMul(data, tid, 64, 4); // twiddle factor multiplication
 localShuffle(data, sMemx, sMemy, tid, (((tid » 4)* 64) +(tid &amp; 15)));
 // four radix-4 function calls
 fftRadix4Pass(data); // radix-4 function number 1
 fftRadix4Pass(data + 4); // radix-4 function number 2
 fftRadix4Pass(data + 8); // radix-4 function number 3
 fftRadix4Pass(data + 12); // radix-4 function number 4
 // coalesced global writes
 globalStores(data, out, 64);
Apple的网站上可以发现傅立叶变换的例子
平行合并排序法.
使用 Python 3.x 搭配 PyOpenCL 与 NumPy
import io
import random
import numpy as np
import pyopencl as cl
def dump_step(data, chunk_size):
 """显示排序过程"""
 msg = io.StringIO(")
 div = io.StringIO(")
 for idx, item in enumerate(data):
 if idx % chunk_size == 0:
 if idx &gt; 0:
 msg.write(' ||')
 div.write(' ')
 div.write(' --')
 else:
 msg.write(' ')
 div.write('------')
 msg.write(' {:2d}'.format(item))
 out = msg.getvalue()
 if chunk_size == 1: print(' ' + '-' * (len(out) - 1))
 print(out)
 print(div.getvalue())
 msg.close()
 div.close()
def cl_merge_sort_sbs(data_in):
 """平行合并排序"""
 # OpenCL kernel 函数程式码
 CL_CODE = 
 kernel void merge(int chunk_size, int size, global long* data, global long* buff) {
 // 取得分组编号
 const int gid = get_global_id(0);
 // 根据分组编号计算责任范围
 const int offset = gid * chunk_size;
 const int real_size = min(offset + chunk_size, size) - offset;
 global long* data_part = data + offset;
 global long* buff_part = buff + offset;
 // 设定合并前的初始状态
 int r_beg = chunk_size » 1;
 int b_ptr = 0;
 int l_ptr = 0;
 int r_ptr = r_beg;
 // 进行合并
 while (b_ptr &lt; real_size) {
 if (r_ptr &gt;= real_size) {
 // 若右侧没有资料，取左侧资料堆入缓冲区
 buff_part[b_ptr] = data_part[l_ptr++];
 } else if (l_ptr == r_beg) {
 // 若左侧没有资料，取右侧资料堆入缓冲区
 buff_part[b_ptr] = data_part[r_ptr++];
 } else {
 // 若两侧都有资料，取较小资料堆入缓冲区
 if (data_part[l_ptr] &lt; data_part[r_ptr]) {
 buff_part[b_ptr] = data_part[l_ptr++];
 } else {
 buff_part[b_ptr] = data_part[r_ptr++];
 b_ptr++;
 # 配置计算资源，编译 OpenCL 程式
 ctx = cl.Context(dev_type=cl.device_type.GPU)
 prg = cl.Program(ctx, CL_CODE).build()
 queue = cl.CommandQueue(ctx)
 mf = cl.mem_flags
 # 资料转换成 numpy 形式以利转换为 OpenCL Buffer
 data_np = np.int64(data_in)
 buff_np = np.empty_like(data_np)
 # 建立缓冲区，并且复制数值到缓冲区
 data = cl.Buffer(ctx, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=data_np)
 buff = cl.Buffer(ctx, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=buff_np)
 # 设定合并前初始状态
 data_len = np.int32(len(data_np))
 chunk_size = np.int32(1)
 dump_step(data_np, chunk_size)
 while chunk_size &lt; data_len:
 # 更新分组大小，每一回合变两倍
 chunk_size «= 1
 # 换算平行作业组数 
 group_size = ((data_len - 1) // chunk_size) + 1
 # 进行分组合并作业
 prg.merge(queue, (group_size,), (1,), chunk_size, data_len, data, buff)
 # 将合并结果作为下一回合的原始资料
 temp = data
 data = buff
 buff = temp
 # 显示此回合状态
 cl.enqueue_copy(queue, data_np, data)
 dump_step(data_np, chunk_size)
 queue.finish()
 data.release()
 buff.release()
def main():
 n = random.randint(5, 16)
 data = []
 for i in range(n):
 data.append(random.randint(1, 99))
 cl_merge_sort_sbs(data)
if __name__ == '__main__':
 main()
执行结果：
 85 || 41 || 64 || 40 || 90 || 29 || 38 || 41 || 64 || 17 || 20 || 41 || 16 || 65 || 83
 41 85 || 40 64 || 29 90 || 38 41 || 17 64 || 20 41 || 16 65 || 83
 40 41 64 85 || 29 38 41 90 || 17 20 41 64 || 16 65 83
 29 38 40 41 41 64 85 90 || 16 17 20 41 64 65 83
 16 17 20 29 38 40 41 41 41 64 64 65 83 85 90