AMD OpenCL大學課程(11)


性能優化

1、線程映射

   所謂線程映射是指某個線程訪問哪一部分數據,其實就是線程id和訪問數據之間的對應關系

合適的線程映射可以充分利用硬件特性,從而提高程序的性能,反之,則會降低性能。

   請參考Static Memory Access Pattern Analysis on a Massively Parallel GPU這篇paper,文中講述線程如何在算法中充分利用線程映射。這是我在google中搜索到的下載地址:http://www.ece.neu.edu/~bjang/patternAnalysis.pdf

   使用不同的線程映射,同一個線程可能訪問不同位置的數據。下面是幾個線程映射的例子:

image

image

      我們考慮一個簡單的串行矩陣乘法:這個算法比較適合輸出數據降維操作,通過創建N*M個線程,我們移去兩層外循環,這樣每個線程執行P個加法乘法操作。現在需要我們考慮的問題是,線程索引空間究竟應該是M*N還是N*M?

image

    當我們使用M*N線程索引空間時候,Kernel如下圖所示:

image

   而使用N*M線程索引空間時候,Kernel如下圖所示:image

    使用兩種映射關系,程序執行結果是一樣的。下面是在nv的卡GeForce 285 and 8800 GPUs上的執行結果。可以看到映射2(及N*M線程索引空間),程序的performance更高。

image

    性能差異主要是因為在兩種映射方式下,對global memory訪問的方式有所不同。在行主序的buffer中,數據都是按行逐個存儲,為了保證合並訪問,我們應該把一個wave中連續的線程映射到矩陣的列(第二維),這樣在A*B=C的情況下,會把矩陣B和C的內存讀寫實現合並訪問,而兩種映射方式對A沒有影響(A由i3決定順序)。

   完整的源代碼請從:http://code.google.com/p/imagefilter-opencl/downloads/detail?name=amduniCourseCode4.zip&can=2&q=#makechanges下載,程序中我實現了兩種方式的比較。結果確實第二種方式要快一些。

   下面我們再看一個矩陣轉置的例子,在例子中,通過改變映射方式,提高了global memory訪問的效率。

image

   矩陣轉置的公式是:Out(x,y) = In(y,x)

   從上圖可以看出,無論采取那種映射方式,總有一個buffer是非合並訪問方式(注:在矩陣轉置時,必須要把輸入矩陣的某個元素拷貝到臨時位置,比如寄存器,然后才能拷貝到輸出矩陣)。我們可以改變線程映射方式,用local memory作為中間元素,從而實現輸入,輸出矩陣都是global memory合並訪問。

image

  下面是AMD 5870顯卡上,兩種線程映射方式實現的矩陣轉置性能比較:

image

    完整代碼:http://code.google.com/p/imagefilter-opencl/downloads/detail?name=amduniCourseCode5.zip&can=2&q=#makechanges

2、Occupancy

    前面的教程中,我們提到過Occupancy的概念,它主要用來描述CU中資源的利用率。

    OpenCL中workgroup被映射到硬件的CU中執行,在一個workgroup中的所有線程執行完之后,這個workgroup才算執行結束。對一個特定的cu來說,它的資源(比如寄存器數量,local memory大小,最大線程數量等)是固定的,這些資源都會限制cu中同時處於調度狀態的workgroup數量。如果cu中的資源數量足夠的的話,映射到同一個cu的多個workgroup能同時處於調度狀態,其中一個workgroup的wave處於執行狀態,當處於執行狀態的workgroup所有wave因為等待資源而切換到等待狀態的話,不同workgroup能夠從就緒狀態切換到ALU執行,這樣隱藏memory訪問時延。這有點類似操作系統中進程之間的調度狀態。我簡單畫個圖,以供參考:

image

  • 對於一個比較長的kernel,寄存器是主要的資源瓶頸。假設kernel需要的最大寄存器數目為35,則workgroup中的所有線程都會使用35個寄存器,而一個CU(假設為5870)的最大寄存器數目為16384,則cu中最多可有16384/35=468線程,此時,一個workgroup中的線程數目(workitem)不可能超過468,
  • 考慮另一個問題,一個cu共16384個寄存器,而workgroup固定為256個線程,則使用的寄存器數量可達到64個。

    每個CU的local memory也是有限的,對於AMD HD 5XXX顯卡,local memory是32K,NV的顯卡local memory是32-48K(具體看型號)。和使用寄存器的情況相似,如果kernel使用過多的local memory,則workgroup中的線程數目也會有限制。

   GPU硬件還有一個CU內的最大線程數目限制:AMD顯卡256,nv顯卡512。

   NV的顯卡對於每個CU內的激活線程有數量限制,每個cu 8個或16個warp,768或者1024個線程。

   AMD顯卡對每個CU內的wave數量有限制,對於5870,最多496個wave。

   這些限制都是因為有限的資源競爭引起的,在nv cuda中,可以通過可視化的方式查看資源的限制情況。

3、向量化

   向量化允許一個線程同時執行多個操作。我們可以在kernel代碼中,使用向量數據類型,比如float4來獲得加速。向量化在AMD的GPU上效果更為明顯,這是因為AMD的顯卡的stream core是(x,y,z,w)這樣的向量運算單元。

>   下圖是在簡單的向量賦值運算中,使用float和float4的性能比較。

image

    kernel代碼為:

image

    完整代碼請從:http://code.google.com/p/imagefilter-opencl/downloads/detail?name=amduniCourseCode6.zip&can=2&q=#makechanges下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM