1、OpenCL架構 OpenCL可以實現混合設備的并行計算,這些設備包括CPU,GPU,以及其它處理器,比如Cell處理器,DSP等。使用OpenCL編程,可以實現可移植的并行加速代碼。[ 但由于各個OpenCL device不同的硬件性能,可能對于程序的優化還要考慮具體的硬件特性 ]
1、opencl架構
OpenCL可以實現混合設備的并行計算,這些設備包括CPU,GPU,以及其它處理器,比如Cell處理器,DSP等。使用OpenCL編程,可以實現可移植的并行加速代碼。[但由于各個OpenCL device不同的硬件性能,可能對于程序的優化還要考慮具體的硬件特性]。
通常OpenCL架構包括四個部分:
- 平臺模型(Platform Model)
- 執行模型(Execution Model)
- 內存模型(Memory Model)
- 編程模型(Programming Model)
2、OpenCL平臺模型
不同廠商的OpenCL實施定義了不同的OpenCL平臺,通過OpenCL平臺,主機能夠和OpenCL設備之間進行交互操作。現在主要的OpenCL平臺有AMD、Nvida,Intel等。OpenCL使用了一種Installable Client Driver模型,這樣不同廠商的平臺就能夠在系統中共存。在我的計算機上就安裝有AMD和Intel兩個OpenCL Platform[現在的OpenCL driver模型不允許不同廠商的GPU同時運行]。
OpenCL平臺通常包括一個主機(Host)和多個OpenCL設備(device),每個OpenCL設備包括一個或多個CU(compute units),每個CU包括又一個或多個PE(process element)。 每個PE都有自己的程序計數器(PC)。主機就是OpenCL運行庫宿主設備,在AMD和Nvida的OpenCL平臺中,主機一般都指x86 CPU。
對AMD平臺來說,所有的CPU是一個設備,CPU的每一個core就是一個CU,而每個GPU都是獨立的設備。
3、OpenCL編程的一般步驟
下面我們通過一個實例來了解OpenCL編程的步驟,假設我們用的是AMD OpenCL平臺(因為本人的GPU是HD5730),安裝了AMD Stream SDK 2.6,并在VS2008中設置好了include,lib目錄等。
首先我們建立一個控制臺程序,最初的代碼如下:
<span> 1:</span> <span>#include</span> <span>"stdafx.h"</span>
<span> 2:</span> <span>#include</span> <CL/cl.h>
<span> 3:</span> <span>#include</span> <stdio.h>
<span> 4:</span> <span>#include</span> <stdlib.h>
<span> 5:</span>
<span> 6:</span> <span>#pragma</span> <span>comment</span> (lib,<span>"OpenCL.lib"</span>)
<span> 7:</span>
<span> 8:</span> <span>int</span> main(<span>int</span> argc, <span>char</span>* argv[])
<span> 9:</span> {
<span> 10:</span> <span>return</span> 0;
<span> 11:</span> }
第一步,我們要選擇一個OpenCL平臺,所用的函數就是
通常,這個函數要調用2次,第一次得到系統中可使用的平臺數目,然后為(Platform)平臺對象分配空間,第二次調用就是查詢所有的平臺,選擇自己需要的OpenCL平臺。代碼比較長,具體可以看下AMD Stream SDK 2.6中的TemplateC例子,里面描述如何構建一個robust的最小OpenCL程序。為了簡化代碼,使程序看起來不那么繁瑣,我直接調用該函數,選取系統中的第一個OpenCL平臺,我的系統中安裝AMD和Intel兩家的平臺,第一個平臺是AMD的。另外,我也沒有增加錯誤檢測之類的代碼,但是增加了一個status的變量,通常如果函數執行正確,返回的值是0。
<span> 1:</span> <span>#include</span> <span>"stdafx.h"</span>
<span> 2:</span> <span>#include</span> <CL/cl.h>
<span> 3:</span> <span>#include</span> <stdio.h>
<span> 4:</span> <span>#include</span> <stdlib.h>
<span> 5:</span>
<span> 6:</span> <span>#pragma</span> <span>comment</span> (lib,<span>"OpenCL.lib"</span>)
<span> 7:</span>
<span> 8:</span> <span>int</span> main(<span>int</span> argc, <span>char</span>* argv[])
<span> 9:</span> {
<span> 10:</span> cl_uint status;
<span> 11:</span> cl_platform_id platform;
<span> 12:</span>
<span> 13:</span> status = clGetPlatformIDs( 1, &platform, NULL );
<span> 14:</span>
<span> 15:</span> <span>return</span> 0;
<span> 16:</span> }
第二步是得到OpenCL設備,
這個函數通常也是調用2次,第一次查詢設備數量,第二次檢索得到我們想要的設備。為了簡化代碼,我們直接指定GPU設備。
<span> 1:</span> <span>#include</span> <span>"stdafx.h"</span>
<span> 2:</span> <span>#include</span> <CL/cl.h>
<span> 3:</span> <span>#include</span> <stdio.h>
<span> 4:</span> <span>#include</span> <stdlib.h>
<span> 5:</span>
<span> 6:</span> <span>#pragma</span> <span>comment</span> (lib,<span>"OpenCL.lib"</span>)
<span> 7:</span>
<span> 8:</span> <span>int</span> main(<span>int</span> argc, <span>char</span>* argv[])
<span> 9:</span> {
<span> 10:</span> cl_uint status;
<span> 11:</span> cl_platform_id platform;
<span> 12:</span>
<span> 13:</span> status = clGetPlatformIDs( 1, &platform, NULL );
<span> 14:</span>
<span> 15:</span> cl_device_id device;
<span> 16:</span>
<span> 17:</span> clGetDeviceIDs( platform, CL_DEVICE_TYPE_GPU,
<span> 18:</span> 1,
<span> 19:</span> &device,
<span> 20:</span> NULL);
<span> 21:</span>
<span> 22:</span> <span>return</span> 0;
<span> 23:</span> }
下面我們來看下OpenCL中Context的概念:
通常,Context是指管理OpenCL對象和資源的上下文環境。為了管理OpenCL程序,下面的一些對象都要和Context關聯起來:
—設備(Devices):執行Kernel程序對象。
—程序對象(Program objects): kernel程序源代碼
—Kernels:運行在OpenCL設備上的函數。
—內存對象(Memory objects): device處理的數據對象。
—命令隊列(Command queues): 設備之間的交互機制。
注意:創建一個Context的時候,我們必須把一個或多個設備和它關聯起來。對于其它的OpenCL資源,它們創建時候,也要和Context關聯起來,一般創建這些資源的OpenCL函數的輸入參數中,都會有context。
這個函數中指定了和context關聯的一個或多個設備對象,properties參數指定了使用的平臺,如果為NULL,廠商選擇的缺省值被使用,這個函數也提供了一個回調機制給用戶提供錯誤報告。
現在的代碼如下:
<span> 1:</span> <span>#include</span> <span>"stdafx.h"</span>
<span> 2:</span> <span>#include</span> <CL/cl.h>
<span> 3:</span> <span>#include</span> <stdio.h>
<span> 4:</span> <span>#include</span> <stdlib.h>
<span> 5:</span>
<span> 6:</span> <span>#pragma</span> <span>comment</span> (lib,<span>"OpenCL.lib"</span>)
<span> 7:</span>
<span> 8:</span> <span>int</span> main(<span>int</span> argc, <span>char</span>* argv[])
<span> 9:</span> {
<span> 10:</span> cl_uint status;
<span> 11:</span> cl_platform_id platform;
<span> 12:</span>
<span> 13:</span> status = clGetPlatformIDs( 1, &platform, NULL );
<span> 14:</span>
<span> 15:</span> cl_device_id device;
<span> 16:</span>
<span> 17:</span> clGetDeviceIDs( platform, CL_DEVICE_TYPE_GPU,
<span> 18:</span> 1,
<span> 19:</span> &device,
<span> 20:</span> NULL);
<span> 21:</span> cl_context context = clCreateContext( NULL,
<span> 22:</span> 1,
<span> 23:</span> &device,
<span> 24:</span>
<span> 25:</span>
<span> 26:</span> <span>return</span> 0;
<span> 27:</span> }
接下來,我們要看下命令隊列。在OpenCL中,命令隊列就是主機的請求,在設備上執行的一種機制。
- 在Kernel執行前,我們一般要進行一些內存拷貝的工作,比如把主機內存中的數據傳輸到設備內存中。
另外要注意的幾點就是:對于不同的設備,它們都有自己的獨立的命令隊列;命令隊列中的命令(kernel函數)可能是同步的,也可能是異步的,它們的執行順序可以是有序的,也可以是亂序的。
命令隊列在device和context之間建立了一個連接。
命令隊列properties指定以下內容:
- 是否亂序執行(在AMD GPU中,好像現在還不支持亂序執行)
- 是否啟動profiling。Profiling通過事件機制來得到kernel執行時間等有用的信息,但它本身也會有一些開銷。
如下圖所示,命令隊列把設備和context聯系起來,盡管它們之間不是物理連接。
添加命令隊列后的代碼如下:
<span> 1:</span> <span>#include</span> <span>"stdafx.h"</span>
<span> 2:</span> <span>#include</span> <CL/cl.h>
<span> 3:</span> <span>#include</span> <stdio.h>
<span> 4:</span> <span>#include</span> <stdlib.h>
<span> 5:</span>
<span> 6:</span> <span>#pragma</span> <span>comment</span> (lib,<span>"OpenCL.lib"</span>)
<span> 7:</span>
<span> 8:</span> <span>int</span> main(<span>int</span> argc, <span>char</span>* argv[])
<span> 9:</span> {
<span> 10:</span> cl_uint status;
<span> 11:</span> cl_platform_id platform;
<span> 12:</span>
<span> 13:</span> status = clGetPlatformIDs( 1, &platform, NULL );
<span> 14:</span>
<span> 15:</span> cl_device_id device;
<span> 16:</span>
<span> 17:</span> clGetDeviceIDs( platform, CL_DEVICE_TYPE_GPU,
<span> 18:</span> 1,
<span> 19:</span> &device,
<span> 20:</span> NULL);
<span> 21:</span> cl_context context = clCreateContext( NULL,
<span> 22:</span> 1,
<span> 23:</span> &device,
<span> 24:</span> NULL, NULL, NULL);
<span> 25:</span>
<span> 26:</span> cl_command_queue queue = clCreateCommandQueue( context,
<span> 27:</span> device,
<span> 28:</span> CL_QUEUE_PROFILING_ENABLE, NULL );
<span> 29:</span>
<span> 30:</span> <span>return</span> 0;
<span> 31:</span> }
?原文作者:邁克老狼