OpenCL

OpenCL（Open Computing Language，中譯：開放計算語言），是一個為異構平臺編寫程式的框架，此異構平臺可由 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 釋出。

1.1 路線圖

在 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 的裝載器和分層以及 “靈活配置”，以便在多種加速型別上靈活部署。

1.2 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 版本，具有缺陷修正和針對多裝置的一個新擴充套件。

範例

2.1 快速傅立葉變換

一個快速傅立葉變換的式子：

//create a compute context with GPU device

context = clCreateContextFromType (NULL, CLDEVICETYPE_GPU, NULL, NULL, NULL);

//create a command queue

queue = clCreateCommandQueue (context, NULL, 0, NULL);

//allocate the buffer memory objects

memobjs0 = clCreateBuffer (context, CLMEMREADONLY | CLMEMCOPYHOSTPTR, sizeof (float)2numentries, srcA, NULL);

memobjs1 = clCreateBuffer (context, CLMEMREADWRITE, sizeof (float)2numentries, NULL, NULL);

//create the compute program

program = clCreateProgramWithSource (context, 1, &fft1D1024kernel_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 *)&memobjs0);

clSetKernelArg (kernel, 1, sizeof (cl_mem),(void *)&memobjs1);

clSetKernelArg (kernel, 2, sizeof (float)(localworksize0+1)16, NULL);

clSetKernelArg (kernel, 3, sizeof (float)(localworksize0+1)16, NULL);

//create N-D range object with work-item dimensions and execute kernel

globalworksize0 = num_entries;

localworksize0 = 64;

clEnqueueNDRangeKernel (queue, kernel, 1, NULL, globalworksize, localworksize, 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 fft1D1024 (global float2 in, _global float2 out,

local float sMemx, local float sMemy){int tid = getlocalid (0);

int blockIdx = getgroupid (0) * 1024 + tid;

float2 data16;

//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 & 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 & 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 的網站上可以發現傅立葉變換的例子

2.2 平行合併排序法

使用 Python 3.x 搭配 PyOpenCL 與 NumPy

import io

import random

import numpy as np

import pyopencl as cl

def dumpstep (data, chunksize):

"""顯示排序過程"""

msg = io.StringIO

div = io.StringIO

for idx, item in enumerate (data):

if idx % chunk_size == 0:

if idx > 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 clmergesortsbs (datain):

"""平行合併排序"""

OpenCL kernel 函式程式碼

CL_CODE =

kernel void merge (int chunk_size, int size, global long data, global long buff) {

// 取得分組編號

const int gid = getglobalid (0);

// 根據分組編號計算責任範圍

const int offset = gid * chunk_size;

const int realsize = min (offset + chunksize, size) - offset;

global long* data_part = data + offset;

global long* buff_part = buff + offset;

// 設定合併前的初始狀態

int rbeg = chunksize >> 1;

int b_ptr = 0;

int l_ptr = 0;

int rptr = rbeg;

// 進行合併

while (bptr = realsize) {

// 若右側沒有資料，取左側資料堆入緩衝區

buffpartbptr = datapartlptr++;

} else if (lptr == rbeg) {

// 若左側沒有資料，取右側資料堆入緩衝區

buffpartbptr = datapartrptr++;

} else {

// 若兩側都有資料，取較小資料堆入緩衝區

if (datapartlptr

執行結果：

--------------------------------------------------------------------------------------

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

--------------------------------------------------------------------------------------