這章將會說明一些kernel優化的小技巧。算法
一個複雜的應用程序可能包含不少步驟。對於OpenCL的移植性和優化,可能會問須要開發有多少個kernel。這個問題很難回答,由於這涉及到不少的因素。下面是一些準則:數組
上面的要求能夠經過執行如下操做實現:ide
OpenCL支持一些編譯選項,參考文獻的《The OpenCLSpecification》的5.6.4節中進行了定義。編譯選項能夠經過APIsclCompileProgram和clBuildProgram傳遞。多個編譯選項能夠結合,以下所示。函數
clBuildProgram( myProgram,性能
numDevices,優化
pDevices,ui
「-cl-fast-relaxed-math 」,spa
NULL,操作系統
NULL );指針
經過這些選項,開發者可以針對他們本身的需求使能某些功能。好比,使用-cl-fast-relaxed-math,kernel會編譯成使用快速數學函數而不是OpenCL標準函數,每個OpenCL的說明中OpenCL標準函數都有很高的精度要求。
OpenCL標準在OpenCL C語言中定義了許多數學函數,默認狀況下,由於OpenCL規範說明書的要求,全部的數學函數都必須知足IEEE 754 單精度的浮點精度數學要求。Adreno GPU有一個內嵌的硬件模塊,EFU(elementary function unit 基本函數單元),來加速一些初級的數學函數。對於許多EFU不能直接支持的數學函數,能夠經過結合EFU和ALU操做來優化,或者經過編譯器使用複雜的算法來模擬進行優化。表8-1展現了OpenCL-GPU 數學函數的列表,並按照他們的相對性能來分類的。使用更好性能的函數是個較好的方法,好比使用A類中的函數
表8-1 OpenCL數學函數的性能(符合IEEE 754標準)
類別 |
實現 |
函數(可參考OpenCL標準獲取更多細節) |
A |
僅簡單使用ALU指令 |
ceil,copysign,fabs,fdim, floor,fmax, fmin, fract,frexp,ilogb, mad, maxmag,minmag,modf,nan,nextafter,rint,round,trunk |
B |
僅使用EFU,或者EFU機上簡單的ALU指令 |
asin,asinpi,atan,atanh,atanpi,cosh,exp,exp2,rsqrt,sqrt,tanh |
C |
ALU,EFU,和位操做的結合 |
acos,acosh, acospi,asinh, atan, atan2pi,cbrt,cos,cospi,exp10,expml,fmod,hypot,ldexp,log,log10,loglp,log2,logb,pow,remainder,remquo,sin,sincos,sinh,sinpi |
D |
複雜的軟件模擬 |
erf,erfc,fma,lgamma,lgamma_r,pown,powr,rootn,tan,tanpi,tgamma |
另外,若是應用程序對精度不敏感的話,開發者能夠選擇使用內部的或者快速的數學函數來替代標準的數學函數。表8-2 總結了使用數學函數時的3個選項。
原始的:int c = a/b ;// a和b都是整數。
使用內部指令:
int c =(int)native_divide((float)(a)),(float)(b));
表8-2 基於精度/性能的數學函數選擇
數學函數 |
定義 |
怎麼使用 |
精度要求 |
性能 |
典型應用 |
標準 |
符合IEEE754單精度浮點要求 |
默認 |
嚴格 |
低 |
科學計算,對精度敏感的狀況下 |
快速 |
低精度的快速函數 |
kernel編譯選項 -cl-fast-relaxed-math |
中等 |
中等 |
許多圖像,音頻和視覺的用例中 |
內部 |
直接使用硬件計算 |
使用native_function替換kernel中的函數 |
低,與供應商有關 |
高 |
對精度損失不敏感的狀況下的圖像,音頻,和視覺用例中 |
循環展開一般是一個好方法,由於它可以減小指令執行的耗時從而提升性能。Adreno編譯器一般能基於試探法自動地將循環展開。然而,有時候編譯器選擇不將循環徹底展開,由於基於考慮到,寄存器的分配預算,或者編譯器由於缺乏某些信息不能將它展開等因素。在這些狀況下,開發者能夠給編譯器一個提示,或者手動的強制將循環展開,以下所示:
通常地,當在同一個wave中的work item有不一樣的執行路徑時,那麼GPU就不是那麼高效率。對於某些分支,一些work time必須執行,從而致使較低的GPU使用率,就像圖8-1所示。並且,像if-else的條件判斷代碼一般會引發硬件的控制流邏輯,這個是很是耗時的。
圖8-1 繪圖表示出如今兩個wave中的分支狀況
有一些方法能夠用來避免或者減小分支和條件判斷。在算法層面,一種方法是將進入同一分支的work item組成一個不可分的wave。在kernel層面,一些簡單的分叉/條件判斷能夠轉變成快速的ALU操做。在9.2.6節中一個例子中,有耗時的控制流邏輯的一個三元操做被轉變成一個ALU操做。其餘的方式是使用相似於select函數,這個可能會使用快速的ALU操做來替代控制流邏輯。
許多操做可能會獲取圖像邊界外的像素點,好比濾波,變換等。爲了更好地處理邊界,能夠考慮下面的選擇:
從Adreno A5X GPU開始,64位操做系統逐漸成爲主流,並且許多的Adreno GPU支持64位操做系統。64位操做系統中最重要的改變是內存空間將能徹底覆蓋4GB,並且CPU支持64位指令集。
當GPU能夠獲取64位內存空間時,它的使用將會引發額外的複雜性,並且可能會影響性能。
64位的內存地址在許多狀況下會提高編寫OpenCL kernel的複雜度,開發者必需要當心。強烈建議避免在kernels中定義size_t類型的變量。對於64位操做系統,在kernel中定義成size_t的變量可能會被當成64位長度的數據。Adreno GPUs必須使用32位寄存器來模擬64位。所以,size_t類型的變量會須要更多的寄存器資源,從而由於可用的wave變少和更小的workgroup大小致使性能退化。因此,開發者應該使用32位或者更短的數據類型來替代size_t.
對於OpenCL中返回size_t的內嵌函數,編譯器會根據它所知道的信息嘗試推導並限制數據範圍。好比, get_local_id返回的數據類型爲size_t,儘管local_id永遠不會超過32位。在這種狀況,編譯器嘗試使用一個短的數據類型來替代。可是,更好的方法是,給編譯器提供關於數據類型的最充分的信息,而後編譯器能夠產生更好的優化代碼。
OpenCL2.0 介紹了一個新的特性,叫作通常性的內存地址空間,在這個地址空間中,指針不須要指定它的地址空間,在OpenCL2.0以前,指針必須指定它的地址空間,好比指定爲是local,private,或者global。在通常性的地址空間中,指針能夠動態地被指定爲不一樣的地址空間。
這個特性下降了開發者的代碼基礎並且能重複使用已經存在的代碼,使用通常性的內存地址空間會有輕微的性能損失,由於GPU SP硬件須要動態的指出真正的地址空間。若是開發者清楚知道變量的內存空間,建議清晰地定義內存地址。這將會減小編譯器的歧義,從而會有更好的機器代碼進而提高性能。
還有不少其餘的優化技巧,這些技巧看起來很小,可是一樣能夠提升性能,這些技巧以下所示: