cuda性能優化

CUDA 優化的最終目的是:在最短的時間內,在允許的誤差範圍內完成給定的計算任務。在這裏,“最短的時間”是指整個程序運行的時間,更側重於計算的吞吐量,而不是單個數據的延遲。在開始考慮使用 GPU 和 CPU 協同計算之前,應該先粗略的評估使用 CUDA 是否能達到預想的效果,包括以下幾個方面:

精度:目前 GPU 的單精度性能要遠遠超過雙精度性能,整數乘法、求模、求餘等運算的指令吞吐量也較爲有限。在科學計算中,由於需要處理的數據量巨大,往往採用雙精度或者四精度才能獲得可靠的結果,目前的 Tesla 架構還不能很好的滿足高精度計算的需要。如果你的計算需要很高的精度,或者需要進行很多輪的迭代,最好考慮在關鍵的步驟中使用雙精度,而在其他部分仍然使用單精度浮點以獲得指令吞吐量和精度的平衡。而如果你對精度有更高的要求,那麼現在的架構還不能太高的加速比。不過,在 2010 年將會普及的下一代架構中,雙精度浮點和整數處理能力將有很大的提升,這種情況會有根本性的改變。

延遲:目前 CUDA 還不能單獨爲某個處理核心分配任務,因此必須先緩衝一定量的數據,再交給 GPU 進行計算。這樣的方式可以獲得很高的數據吞吐量,不過單個數據經過緩衝、傳輸到 GPU 計算、再拷貝回內存的延遲就比直接由 CPU 進行串行處理要長很多。如果對應用實時性要求很高,比如必須在數毫秒內完成對一個輸入的處理,那麼使用 CUDA 可能會影響系統的整體性能。對於要求人機能夠實時交互的系統,應該將延遲控制在數十毫秒,以響應用戶的輸入。通過減小緩衝,可以減小延遲,但至少要保證每個內核程序處理的一批數據能夠讓 GPU 滿負荷工作。不過在大多數情況下,在計算吞吐量較大,需要由 GPU 才能實時實現的系統,投入相同成本使用 CPU 很難做到接近實時。如果確實對實時性和吞吐量都有很高要求,應該考慮 ASIC 、 FPGA 或者 DSP 實現,這需要更多的投入,更長的開發時間和硬件開發經驗。

計算量:如果計算量太小,那麼使用 CUDA 是不合算的。衡量計算量有絕對和相對兩種方式。

從絕對量來說,如果要優化的程序使用頻率比較低,並且每次調用需要的時間也可以接受,那麼使用 CUDA 優化並不會顯著改善使用體驗。對於一些計算量非常小(整個程序在 CPU 上可以在幾十毫秒內完成)的應用來說,使用 CUDA 計算時在 GPU 上的執行時間無法隱藏訪存和數據傳輸的延遲,此時整個應用程序需要的時間反而會比 CPU 更長。此外,雖然GPU 的單精度浮點處理能力和顯存帶寬都遠遠超過了 CPU ,但由於 GPU 使用 PCI-E 總線與主機連接,因此它的輸入和輸出的吞吐量受到了 IO 帶寬的限制。當要計算的問題的計算密集度很低時,執行計算的時間遠遠比 IO 花費的時間短,那麼整個程序的瓶頸就會出現在 PCI-E 帶寬上。此時無論如何提高浮點處理能力和顯存帶寬,都無法提高系統性能。

相對的計算量指可以並行的部分在整個應用中所佔的時間。如果整個應用中串行部分佔用時間較長,而並行部分較短,那麼也需要考慮是否值得使用 GPU 進行並行計算。例如,假設一個程序總的執行時間爲 1.0 ,其中串行部分佔 0.8 ,而並行部分只佔 0.2 ,那麼使用 GPU 將並行部分加速 10 倍,總的執行時間也只能從 1.0 降低到 0.82 。即使是在 CPU 和 GPU 可以同時並行計算的應用中,執行時間也至少是 CPU 串行計算需要的 0.8 。只有在並行計算佔用了絕大多數計算時間的應用中,使用 CUDA 加速才能獲得很高的加速比。不過,隨着 GPU+CPU 並行計算的普及和 GPU 架構的進一步改進,即使獲得的加速比較小,也可能會由 GPU 執行。

完成對 GPU 加速能夠達到的效果的粗略評估後,就可以開始着手編寫程序了。爲了在最短的時間內完成計算,需要考慮算法、並行劃分、指令流吞吐量、存儲器帶寬等多方面問題。總的來說,優秀的 CUDA 程序應該同時具有以下幾個特徵:

在給定的數據規模下,選用算法的計算複雜度不明顯高於最優的算法;

active warp 的數量能夠讓 SM 滿載,並且 active block 數量大於 2 ,能夠有效的隱藏訪存延遲;

當瓶頸出現在指令流時,指令流效率已經經過了充分優化;

當瓶頸出現在訪存或者 IO 時,已經選用了恰當的存儲器和和存儲器訪問方式以獲得最大帶寬。

按照開發流程的先後順序, CUDA 程序的編寫與優化需要解決以下問題:

1 .確定任務中的串行部分和並行部分,選擇合適的算法。首先,需要將問題分爲幾個步驟,並確定哪些步驟可以用並行算法實現,並確定要使用的算法。

2 .按照算法確定數據和任務的劃分方式,將每個需要並行實現的步驟映射爲一個滿足 CUDA 兩層並行模型的內核函數。在這裏就要儘量讓每個 SM 上擁有至少 6 個活動 warp 和至少 2 個活動線程塊。

3 .編寫一個能夠正確運行的程序,作爲優化的起點。程序必須能夠穩定運行,不能發生存儲器泄漏的情況。爲了保證結果正確,在必要的時候必須使用 memory fence 、同步、原子操作等功能。在精度不足或者發生溢出時必須使用雙精度浮點或者更長的整數類型。

3 .優化顯存訪問,避免顯存帶寬成爲瓶頸。在顯存帶寬得到完全優化前,其他優化不會產生明顯結果。顯存訪問優化中可以使用的技術包括:

將可以採用相拓撲實現的幾個 kernel 合併爲一個,減少對顯存訪問;

除非非常必要,應該盡力避免將線程私有變量分配到 local memory ;

爲滿足合併訪問,採用 cudaMallocPitch() 或者 cudaMalloc3D() 分配顯存;

爲滿足合併訪問,對數據類型進行對齊(使用 __align );

爲滿足合併訪問,保證訪問的首地址從 16 的整數倍開始,如果可能,儘量讓每個線程一次讀 32bit ;

在數據只會被訪問一次,並且滿足合併訪問的情況下可以考慮使用 zerocopy ;

在某些情況下,考慮存儲器控制器負載不均衡造成分區衝突的影響。

使用用有緩存的常數存儲器和紋理存儲器提高某些應用的實際帶寬。

4 .優化指令流。由於編譯器會進行一些優化,而編譯過程基本無法控制,所以指令流優化不一定能獲得立竿見影的效果。但是,仍然有一些準則可以參考,包括:

如果只需要少量線程進行操作,那麼一定記得要使用類似 (if threadID < N) 的方式,避免多個線程同時運行佔用更長時間或者產生錯誤結果;

在不會出現不可接受的誤差的前提下采用 CUDA 算術指令集中的快速指令;

使用 #unroll ,讓編譯器能夠有效的展開循環;

採用原子函數實現更加複雜的算法,並保證結果的正確性;

避免多餘的同步;

如果不產生 bank conflict 的算法不會造成算法效率的下降或者非合併訪問,那麼就應該避免 bank conflict 。

5 .資源均衡。調整 shared memroy 和 register 的使用量。爲了使程序能夠獲得更高的 SM 佔用率,應該調整每個線程處理的數據數量、 shared memory 和 register 的使用量。這需要在三者間進行調整。當每個線程處理的子任務間有一定的公用部分時,可以考慮讓一個線程處理更多的數據來提高指令流和訪存效率。爲了獲得更高的 SM 佔用率,必須控制每個線程的 shared memory 和 register 的使用量。通過調整 block 大小,修改算法和指令,以及動態分配 shared memory ,都可以提高shared 的使用效率。而減小 register 的使用則相對困難,因爲 register 的 使用量並不是由內核程序中聲明的變量大小決定,而是由內核程序中使用寄存器最多的時刻的用量決定的。由於編譯器會盡量減小寄存器的用量,因此實際使用的寄 存器有可能會小於在程序中聲明的量。但是在通常情況下,由於需要暫存中間結果並且一些指令也需要更多的寄存器,一般寄存器用量都大於內核程序中聲明的私有 變量的總數量。使用以下方法可能可以節約一些寄存器的使用:使用 shared memory 存儲變量;使用括號更加明確的表示每個變量的生存週期;用對 [u]long 型或的處理代替對兩個相鄰的 [u]short 型或者四個相鄰的 [u]char 型的處理;使用佔用寄存器較小的等效指令代替原有指令,如用 __sin 函數代替 sin 函數。不過,由於不能對編譯器的優化過程進行控制,即使使用了這些手段也不一定能減小寄存器的用量。值得注意的是,採用 --maxrregcount 編譯選項只是讓編譯器將超出限制的私有寄存器分配在 local memory 中,造成較大的訪存延遲。

6. 與主機通信優化。由於 PCI-E 帶寬相對較小,應該儘量減少 CPU 與 GPU 間傳輸的數據量,並通過一些手段提高可用帶寬。可用的技術包括:

使用 cudaMallocHost 分配主機端存儲器,可以獲得更大的帶寬;

一次緩存較多的數據,再一併傳輸,可以獲得較高的實際帶寬;

需要將結果顯示到屏幕時,直接使用與圖形學 API 互操作功能完成,避免將數據傳回;

       使用流式處理隱藏與主機的通信時間。

       對於用 CUDA C 語 言編寫的程序,按照上述流程進行優化,是比較適合的。不過在優化中,各種因素往往相互制約,很難同時達到最優。讀者需要按照要處理問題的類型,瓶頸出現的 部位和原因具體分析。按照預想進行優化也不是總能達到預想中的效果,有時優化手段反而會降低性能。在實踐中,仍然需要不斷實驗各種優化方法,在不斷試驗與 迭代中一步步排除不可行的方案,最後得到一個比較理想的方案。

使用 CUDA C 並不總是能夠編譯到最優的指令。如果確實必要,可以用 PTX 優化程序中最關鍵的步驟。

除此以外,還要靈活採用宏和模版,動態分配內存和顯存以及動態劃分數據等手段提高程序的通用性,並在處理不同規模、不同數據類型的問題時選用不同的優化策略。

在 2010 年將要推出的新處理器中,各種存儲器的帶寬和延遲會有一定的調整,大部分指令的吞吐量也會有非常顯著的提升。隨着 GPU 架構的進一步改進和編譯器性能的不斷提高,下一代 GPU 上的 CUDA 程序優化工作會變得更加簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章