利用CAL進行並行計算

引言:
      對於初學GPGPU的朋友,基本上都是從Brook+,CUDA C或者OpenCL開始的,因爲這些接口的Kernel都是基於類C語言進行開發的,相對來說容易很多。就現在而言,CUDA C的接口會成熟一些。但是很多人的顯卡是A卡,因爲A卡的Graphics相對好一些,對於這些朋友如果想在自己的機器上進行高性能計算,可以選擇CAL進行開發。CAL(Compute Abstraction Layer)是AMD的GPGPU的硬件抽象層,它提供了一些底層的功能,從而可以使用戶進行高性能的計算。相對前幾種接口而言,開發難度可能會大一些,因爲CAL的Kernel是用類彙編語言進行編寫的,可讀性並沒有類C語言好。

      本文講解了一個利用CAL開發的簡單的控制檯程序。程序的內容非常簡單,就是把兩個數組中每個元素分別乘起來而已。本文適合對於GPGPU有一定了解的朋友。

正文:
      首先我們看下host端,我們需要做些什麼纔可以正確設置起來CAL的環境。

            1. 初始化CAL: calInit,調用這個函數就好。

            2. 打開設備。

            3. 創建Context。

      簡單的三部設置就可以設置好CAL的環境了。設置好環境後,我們需要編譯Kernel代碼。也分爲三部分:

            1. Compile Kernel的代碼(之前需要得到設備的屬性,因爲CAL會爲每個不同的設備編譯優化的代碼)。

            2. 鏈接Object。

            3. 讀取Module。

      其實Host端會比Brook+複雜一些,但是並沒有什麼本質的區別,只不過Brook+是封裝了這些步驟。所以數據設置部分我就不再重複了,代碼裏面有的。我們重點來看一下kernel部分吧。

            il_ps_2_0
            dcl_input_interp(linear) v0.xy__
            dcl_resource_id(0)_type(2d,unnorm)_fmtx(float)_fmtx(float)_fmtx(float)_fmtx(float)
            dcl_resource_id(1)_type(2d,unnorm)_fmtx(float)_fmtx(float)_fmtx(float)_fmtx(float)
            dcl_output_generic o0
            flr r0 , v0
            sample_resource(0)_sampler(0) r1 , r0.xyxx
            sample_resource(1)_sampler(1) r2 , r0.xyxx
            mul o0 , r1 , r2
            end

      以上這個簡短的程序就是CAL的Kernel部分了,對於剛接觸CAL的朋友來說,並不是一眼就能看出來這個程序是做什麼的,所以我貼上等同的OpenCL的代碼好了。

            __kernel void GPUKernel( const __global float4* data1 ,
                                                 const __global float4* data2 ,
                                                 __global float4* result )
            {
                 uint tidx = get_global_id(0);
                 uint tidy = get_global_id(1);
                 uint dim = get_global_size(0);

                 uint tid= tidy * dim + tidx;

                 result[tid] = data1[tid]+data2[tid];
            }

     上面這個Kernel簡單多了,基本沒什麼東西。我就不多做介紹了。我們重點來看下CAL的那個Kernel。

     第一行的il_ps_2_0是說,語言是IL(Intermediate Language),ps代表pixel shader,而2_0代表是pixel shader 2.0版本。做通用計算可以用ps還可以用cs(compute shader),由於前者支持的廣泛一些,所以我們這裏用ps。

     dcl_input_interp(linear) v0.xy__ ,dcl應該是declera的意思,這裏的意思是聲明一個變量,這個變量是根據你的thread來確定的,就相當於Opencl裏面的get_global_id。這個變量有兩點需要注意:

     1. CAL裏面的寄存器是沒有數據類型的概念的。程序員有責任記住每個變量是什麼類型的。這裏的索引並不是int型的,而是float型的。這個索引是從(0.5,0.5)開始的,相鄰的thread的索引差1.0。

     2. 這個索引是2維的,因爲我們外面創建資源的時候創建的是2維的。因爲資源的每一維是有長度限制的,在Radeon 4800Series上是8192,5870上是16384。如果我們要處理更多的數據,一維的資源顯然不能滿足我們的需求,所以我們這裏創建了2維的資源。那麼我們最大可以創建8192*8192個element,而每個element可以有4個float,那麼一共是268435456個數據。這已經是1G的顯存了。不過大多數程序不需要一次處理這麼大的計算量,所以二維的資源完全可以滿足我們的需求。

     下面的兩條指令是聲明資源,unnorm代表資源裏面的數據不是單位化的,後面我們規定了資源裏面每個元素的類型。這裏我們注意到一個細節,每個元素擁有四個float,事實上amd的GPU在進行計算時,這四路是同時計算的。我們把每四個float分別放置到一個element裏面,這樣我們可以充分利用AMD GPU的硬件特性進行計算。

     後面就是輸出結果的聲明。程序的聲明部分到這裏就已經結束了。下面就是計算的部分了,相對來說更容易看一些。

     flr r0 , v0 指令就是取整指令,這裏取整也是四路的,但是隻有前兩路對我們有意義。

     然後進行採樣,就是資源的讀取,這裏由於資源是二維的,所以我們要輸入二維的索引。索引的類型必須爲float。

     下面就是最核心也是最簡單的計算的部分了,把寄存器r1和r2的內容乘起來而已。這樣這個簡單的Kernel就結束了。

     其實前面的聲明對於剛接觸的朋友來說,可能稍微複雜一點,但是熟悉了以後,發現IL也並不是特別難寫的。這個程序同樣是附有源代碼的:
     http://filer.blogbus.com/4730079/resource_47300791259325111j.rar

發佈了25 篇原創文章 · 獲贊 7 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章