當前位置:
首頁 > 科技 > 如何在大線程組下優化GPU佔用率和資源使用?

如何在大線程組下優化GPU佔用率和資源使用?

譯者:劉超(君臨天下)

審校:梁君(君兒)

介紹

本周,我們收到了一篇來自Sebastian Aaltonen的客座文章,他是Second Order有限公司的聯合創始人並且曾經作為Ubisoft公司的高級渲染工程師。Second Order最近宣布了它們的第一個遊戲Claybook!該遊戲看起來非常的棒,它的渲染器十分有新意,使用GPU用非傳統的方法達到該效果。請看Claybook。

Sebastian將會解決他在開發Claybook時遇到的一些有趣的問題:你如何在使用大線程組的情況下優化GPU佔用率和考慮著色器的資源使用問題。

大線程組下的GPU佔用率和資源使用優化

當使用一個計算著色器時,考慮線程組的大小對性能的影響是非常重要的。有限的寄存器空間,存儲延遲和SIMD佔用率,每一個都以不同的方式影響著色器的性能。本文將會討論潛在的性能問題,以及如何正確的使用相關技術和優化來顯著的提高著色性能。本文將集中關注大線程組的問題,但是這些提示和技巧對於一般的問題也是有所幫助的。

背景

DirectX11渲染引擎5的計算著色器規範(2009)要求每一個線程組允許的最大內存大小是32KB,並且最大的線程組包含1024個線程。對於最大的寄存器計數並沒有指定,如果寄存器存在一定的壓力需求,編譯器將會從寄存器溢出到內存中。然而,由於存儲延遲,溢出將會對性能產生顯著的負面影響,這些問題應該在代碼中被避免。

AMD GPU允許在單個計算單元(CU)上同時執行兩組包含1024個線程的線程組。然而,為了佔有率最大化,著色器必須最小化寄存器和LDS的使用,以便所有線程的資源請求在計算單元上是合適的。

AMD GCN 計算單元(CU)

以下是一個GCN計算單元的架構:

一個GCN計算單元(CU)包含四個SIMDs(單指令流多數據流),每一個包含一個包含32位的VGPRs(矢量通用寄存器)的64KB寄存器文件,對於每一個計算單元(CU)總共擁有65536個VGPRs。每一個計算單元(CU)同時還包含一個32位的SGPRs(標量通用寄存器)寄存器文件。直到GCN3,每一個計算單元(CU)包含512個SGPRs,從GCN3開始,該數字激增到800。每一個計算單元(CU)能夠包含3200個SGPRs,或者12.5KB的寄存器文件。

用於計算單元運行的預定任務的最小單元稱之為wave,每一個wave包含64個線程。計算單元上的四個SIMDs的每一個可以最大調度10個並發wave。在等待完成內存操作時,計算單元可能會掛起一個wave,來執行另外一個wave。這會有助於隱藏延遲,以及最大限度的利用計算單元的計算資源。

SIMD的VGPR的文件大小引入了一個重要的限制:SIMD的VGPRs被均勻的分配到了已經激活的wave的線程中。如果一個著色器需要用到比實際更多的VGPRs,SIMD將不會執行最優的wave數量。佔有率(occupancy):GPU在給定時間能夠執行並行工作的數目,其結果會受到影響。

每一個GCN計算單元具有64KB本地數據共享(LDS)。LDS用來存儲計算著色器的線程組的共享數據。Direct3D限制了單個線程組的共享數據數量為32KB。因此,為了充分的利用LDS,我們需要在每個計算單元上至少運行兩個線程組。

大線程組資源目標

本文使用的著色器的例子是一個線程組大小為1024KB的複雜GPGPU物理解算器。該著色器使用最大線程組和最大規模的線程組共享內存。得益於大規模的線程組,因為它解決了在多個處理過程中使用共享內存作為臨時存儲空間的物理約束問題。越大的線程組尺寸意味著可以處理更大的數據,而不再需要將臨時結果寫入到全局內存中。

現在,讓我們討論一下有效的運行1024KB線程組時必須面對的資源目標:

寄存器:為了使GPU飽和,每一個計算單元必須被分配到兩個大小為1024KB的線程組。對於整個計算單元提供的65536個可用的的VGPRs,每一個線程在任意時刻能夠最多請求32個VGPRs。

組間共享內存:GCN擁有大小為64KB的LDS。我們能夠使用全部的32KB組間共享內存,來適應每個計算單元的兩個線程組。

如果著色器超出了這些限制,在計算單元上將沒有足夠的資源來同時運行兩個線程組。32個VGPR的目標是很難達到的。我們將首先討論如果你無法達到這個目標將面臨的問題,然後,共同探討解決該問題的辦法以及如何去避免這個問題。

問題:每一個計算單元採用單線程組

考慮以下情況,一個應用程序使用最大為1024KB的線程組,但是著色器需要40個VGPRs。在這種情況下,每一個計算單元同時只能執行一個線程組。如果運行兩個線程組,或者是2048個線程,將會需要81920個VGPRs,遠遠超過目前在計算單元上的可使用的65536個VGPRs。

1024個線程將會產生16個包含64個線程的waves,這些waves被均勻的分配給SIMDs,每一個SIMD包含4個waves。我們之前了解到,最佳的佔用率和延遲隱藏需要10個waves,因此,4個waves只有40%的佔有率。這顯著的降低了GPU的潛在的延遲隱藏能力,從而降低了SIMD的利用率。

假設你使用的1024KB的線程使用最大為32KB的LDS。當僅有一個線程組運行時,50%的LDS沒有被利用,它被留給第二個線程組使用,由於寄存器的壓力該線程組並不存在。對於總共40960個VGPRs,或者160KB,每一個線程只有40個VGPRs被寄存器文件使用。因此,每一個計算單元上有96KB(37.5%)的寄存器文件被浪費。

如你所見,由於我們超出了VGPR的使用範圍,如果每個計算單元僅有一個合適的線程組,使用最大線程組會輕易的導致GPU資源利用率的下降。

當評估潛在的線程組的大小配置時,考慮GPU的資源生命周期是非常重要的。

GPU同時為一個線程組分配和釋放所有的資源。寄存器,LDS,wave必須在線程組執行之前被分配好,並且當線程組的最後一個wave執行結束時,所有的線程組資源將被釋放。因此,如果計算單元上僅有一個合適的線程組,由於每一個線程組必須等待之前的線程組結束之後才能啟動,導致分配和釋放之間不會發生重疊現象。由於內存延遲是無法預測的,因此線程組中的waves將會在不同的時間內結束。由於在下一個線程組中的waves在上一個線程組中的所有waves沒有完成之前無法啟動,因此導致佔用率下降。

大的線程組傾向於使用大量的LDS。LDS的訪問與壁壘(barriers)是同步的(GroupMemoryBarrierWithGroupSync,HLSL中的link)。直到同一個線程組裡的所有的waves完成之前,每一個壁壘會阻止其它程序繼續執行。理想情況下,計算單元在等待壁壘時,能夠執行別的線程組。

不幸的是,在我們的示例中,我們只有一個線程組在運行。當在計算單元上只有一個線程組運行時,壁壘將所有的waves限制在同一個著色器指令集上。該指令集在兩個壁壘之間常常是單調的,因此在線程組裡的所有的waves將會傾向於同時載入內存。因為壁壘阻礙了後續著色器獨立部分的進行,因此沒有機會使用計算單來進行有效的能夠隱藏存儲延遲的ALU工作。

解決方案:每個計算單元使用兩個線程組

如果每一個計算單元擁有2個線程組,將會顯著的減少這些問題。所有的線程組趨向於在不同的時間完成任務,在不同的時間遇到不同的障礙,改善指令集並降低佔用率下降的問題。SIMDs會被更好的利用,將會為延遲隱藏提供更多的機會。

我最近優化了一個1024線程組的著色器。最開始它使用48個VGPRs,因此只有一個線程組運行在每個計算單元上。將VGPR使用率降低至32個,不需要別的任何優化,平台性能提升了50%。

每個計算單元使用兩個線程組是最大化線程組個數的最佳情況。然而,及時使用兩個線程組,佔有率的波動也無法徹底消除。在選擇大型線程組之前,分析其優勢和劣勢十分重要。

大線程組應該何時使用

解決該問題最簡單的方法是完全避免該問題的出現。我提到的許多問題可以通過使用小的線程來解決。如果你的著色器不需要LDS,則根本不需要使用大的線程組。

當不需要LDS時,你應當選擇大小在64-256個線程之間的線程組。AMD推薦大小為256的線程組作為默認選擇,因為其最適合分布演算法。單個wave,64個線程,線程組同樣有他們的作用:GPU能夠在wave結束之後儘快的釋放資源,當所有的wave確保在鎖定狀態進行時,AMDs的著色器編譯器將會釋放所有的內存壁壘。具有高波動的工作負載,從單個wave工作線程組中收穫很多,例如渲染我們的遊戲Claybook時用到的球面跟蹤演算法。

然而,LDS是其它著色階段所缺失的,但是在計算著色器中引人注目的、非常有用的一個特性。當正確的使用時,它能夠更大的提升性能。將公共數據一次載入到LDS中——而不是每一個線程執行單獨的載入——減少了冗餘內存的訪問。高效的使用LDS能夠降低L1高速緩存的失敗和廢棄,同樣對存儲延遲和渲染管線的推遲也有幫助。

當線程組的尺寸降低時,1024線程組所遇到的問題也會顯著減少。大小為512的線程組會好很多:每一個計算單元最多有5個線程組。但是你仍需要遵守32個VGPR的限制用來達到良好的佔有率。

鄰域處理

許多通用的後置處理器(例如:抗鋸齒、模糊、腐蝕和重建)需要該元素附近的鄰域信息。通過使用LDS消除冗餘的內存訪問將會顯著的提高這些濾波器的性能(某些情況可達到30%)。

假設我們有一個2D輸入,每一個線程負責單獨像素的渲染,我們可以看到每一個線程必須檢索它的初始值以及相鄰的八個像素。而每一個鄰域像素也需要檢索線程的初始值。此外,每一個鄰域線程需要中心值。這導致了許多冗餘的載入操作。在一般情況下,每一個像素將會被9個不同的線程訪問。如果沒有LDS,每一個像素需要被載入9次——每一個線程都需要載入一次。

通過首先將所需要的數據載入LDS,並且後續的內存載入操作全部使用LDS載入來代替,這樣可以顯著的減少全局設備內存的訪問次數以及潛在的緩存超載問題。

當在一個線程組裡有大量的共享數據時,LDS是最有效的。越大的鄰域導致越大的線程組尺寸,導致存在更多的可被共享的有效數據,會更進一步減少冗餘載入。

讓我們假設1鄰域和一個矩形的2維線程組。線程組應當載入所有的像素到線程組區域內,並在1鄰域的邊界處滿足邊界條件。一個長度為X的矩形區域需要X^2個內部像素和4X+4個邊界像素。內部有效載荷成二次平方增長,邊界像素是只讀的,為線性增長。

具有單像素邊界的一個8X8的線程組包含64個內部像素和36個邊界像素,總共有100個負載。這需要56%的額外開銷。

現在考慮一個16X16的線程組。有效載荷包含256個像素,還包含額外的68個邊界像素。儘管有效載荷的尺寸是額外載荷的四倍,額外載荷僅68個像素,佔了27%。如果將線程組的大小增加一倍,我們能夠顯著的減少額外開銷。在可能的最大的線程組包含1024個線程——一個32X32的矩形區域,對於讀取132個邊界像素的額外開銷僅占負載的13%。

3維線程組效果更好,因為線程組體積的增長要比邊界的增長快很多。對於一個小的4X4X4的線程組,有效載荷包含64個元素。一個空的6X6X6的立方體,需要216個元素,額外開銷佔到了70%。然而,一個8X8X8的線程組包含512個內部像素和488個邊界像素,額外開銷佔到48%。對於小的線程組,鄰域的額外開銷是巨大的,但是隨著線程組尺寸的增加,性能會提高。很明顯,大線程組有其用途。

使用LDS的多個處理過程

有許多演算法需要多次處理。簡單的存儲在全局內存中,會顯著的消耗內存帶寬。

有時候每一個獨立的部分,或者是很小的「孤島」問題,通過將中間結果存儲在LDS中,將它分解為多個步驟或多個處理過程。單個計算著色器執行所有的所需要的步驟並將每個步驟的中間值寫入到LDS中。僅將結果寫入到內存中。

物理求解器是應用該方法的一個很好的應用。向Gauss-Seidel這種迭代技術需要多個步驟來得到滿足所有約束條件的穩定結果。該問題能夠被分解為多個「孤島」問題:單個連接體的所有粒子被分配到同一個線程組,並且獨立求解。之後的處理過程可能會涉及到內部物體的交互作用,可以採用之前處理過程的中間結果進行計算。

VGPR的使用優化

具有大線程組的著色器變得越來越複雜。達到32個VGPRs的目標是很難的。以下是過去幾年我所學到的一些技巧:

標量數據

GCN設備既有用於維持wave中每個線程的不同狀態的向量(SIMD)單元,又有包含wave中所有線程的單個狀態的標量單元。對於每一個SIMDwave,有一個自身擁有SGPR文件的額外的標量單元在運行。標量寄存器包含一個用於所有wave的單個值。因此,SGPRs有著低於64倍的片上存儲消耗。

GCN著色編譯器會自動發出標量載入指令。如果在編譯時知道載入地址不隨著wave發生變化(意思是該wave中的所有的64個線程有著同樣的地址,非時移wave),編譯器發送一個標量載入的信號,而不是每一個wave獨立的載入數據。對於非時移wave最常用的來源是固定緩存和迭代變數。由於標量單元擁有一個完整的整數指令集,因此在所有非時移wave中計算得到的整數結果同樣也是非時移的wave。這些標量指令與矢量指令SIMD同步發出,而且在執行方面幾乎是不消耗時間的。

計算著色器的內置輸入變數,SV_GroupID同樣也是非時移wave。這是非常重要的,因為它允許離線載入線程組的指定數據到標量寄存器中,從而降低了線程的VGPR壓力。

標量載入指令不支持類型緩存或紋理。如果你想要編譯器載入你的數據到SGPR而不是VGPR,你需要將你的數據從ByteAddressBuffer載入至StructuredBuffer。不要使用類型緩存和紋理來存儲線程組的通用數據。如果你想要從2維/3維數據結構中執行標量數據的載入,你需要自定義地址計算。幸運的是地址計算與標量單元一樣具有完整的整數指令集。

在SGPRs之外運行也是可能的,但是希望渺茫。最常用的方法是通過過多的紋理和採樣器來執行超出SGPR的那部分。紋理可支持8個SGPRs,採樣器可以支持4個SGPRs。DirectX11允許使用單個採樣器和多個紋理。一般的,使用單個採樣器就足夠了。緩存只支持4個SGPRs。緩存和紋理載入指令不需要採樣器,它們應當在不需要濾波器時使用。

示例:每一個線程組通過非時移wave矩陣進行位置變換,例如觀察矩陣或投影矩陣。可以使用4個類型載入指令從緩存區載入4X4的矩陣。然後數據被存儲在16個VGPRs中。這已經消耗了一半的VGPR。取而代之的時,你應當從ByteAddressBuffer中進行4次載入操作。編譯器將生成標量載入,將這些數據存儲在SGPRs中而不是VGPRs。這樣將沒有浪費任何VGPRs。

不需要的數據

在3D圖形學中會經常用到齊次坐標系。在大多數情況下,齊次坐標中的W值不是0就是1。在這種情況下,不要載入或者使用W值。它將在每一個線程中浪費一個VGPR並且生成更多的ALU指令。

同樣地,如果一個4X4的矩陣需要進行投影操作。所有的仿射變換最多需要一個4X3的矩陣。最後一列是(0,0,0,1)。相對於一個完整的4X4的矩陣,一個4X3的矩陣將會節省4個VGPRs/SGPRs。

按位存儲

按位存儲對於節省內存是非常有效的方式。VGPRs是非常寶貴的內存資源——他們運算非常快。幸運的是,GCN提供了快速的,單循環的位提取和位插入演算法。採用這些操作,你能夠將更多的數據高效的存儲在一個32位的VGPR中。

例如,2D整數坐標系可以按照16b+16b的方式進行存儲。HLSL同樣有將2個16位的浮點數存儲或者提取到32位的VGPR的指令(f16tof32 & f32tof16)。這些操作在GCN上是全速率的。

如果你的數據已經按位存儲在內存中,在使用之前不要提取它,而是通過一個uint寄存器或者LDS直接進行載入。

布爾變數

GCN編譯器將布爾變數存儲在一個64位的SGPR中。在wave中每一個通道包含1位。沒有浪費VGPR。不要採用整數或者浮點數來替代布爾變數,這些優化不會起到任何作用。

如果在SGPRs中有更多的布爾變數要存儲,可以考慮採用按位存儲將32位的布爾值存儲在一個VGPR中。GCN有著單循環位提取和位插入操作,能夠快速的進行位操作。此外,你還可以使用countbits()和firstbithigh()/firstbitlow()來進行位縮減和搜索操作。二進位首部求和可以通過countbits()有效的實現,通過對首部位進行掩碼操作,然後求和。

布爾值同樣可以存儲在總是正浮點數的符號位中。abs()和saturate()是GCN上的自由函數。它們作為簡單的輸入/輸出描述符,可以同調用他們的函數一起執行。因此,在符號位中檢索布爾變數幾乎是不消耗時間的。不要使用HLSL中的sign()函數,這會造成編譯器的輸出不是最優的。同樣可以快速的通過測試一個值是否是非負的來決定該符號位的值。

分支和循環

編譯器嘗試將數據從載入到使用之間的代碼距離最大化,以便可以通過它們之間的指令來實現存儲延遲的隱藏。不幸的是,數據在載入和使用之間必須保存在VGPRs中。

可以使用動態循環來降低VGPR的生命周期。依賴循環計數的載入指令無法移動至循環之外。VGPR的生命周期被限制在循環的內部。

使用HLSL中的[loop]屬性強制實現循環。不幸的是,[loop]屬性並非是完全萬無一失的。如果在編譯時請求的迭代次數是已知的,著色編譯器仍然能夠展開該循環。

16位寄存器

GCN3引入了16位寄存器的支持。Vega通過以雙倍速率執行16位的數學運算對其進行擴展。整數和浮點數全部支持。2個16位的寄存器將會被存儲在一個VGPR中。當不需要完整的32位精度時,這是一個簡單的節省VGPR空間的方法。對於2D/3D的地址計算,16位整數是非常適合的(資源載入/存儲和LDS數組)。16位浮點數在後置處理中是非常有用的,尤其是當你處理LDR或者進行後置色調數據映射時。

LDS

當在線程組裡有多個線程載入同樣的數據時,你應當考慮將數據載入至LDS中。這將會大大的降低載入指令的數量以及VGPRs的數量。

當LDS不是立刻需要時,可以將其用作臨時寄存器使用。例如:在開始階段著色器需要載入和使用一段數據,在結束時同樣需要使用這個數據。然而,VGPR的峰值出現在著色器的中間。你可以將這些數據臨時存儲在LDS中,並且稍後當你需要時進行載入。重要的是,這會在峰值期間降低VGPR的使用。

【版權聲明】

原文作者未做權利聲明,視為共享知識產權進入公共領域,自動獲得授權。

----------------------

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!

TAG: |

您可能感興趣

Opera推出遊戲瀏覽器 可讓用戶控制CPU使用率等硬體資源
YouTube將關閉應用內聊天功能 原因為使用率下降
電腦CPU使用率高的解決對策
Intel新技術讓查殺病毒更輕鬆:降低CPU佔用率、粗活交給GPU
Intel核顯將參與安全掃描:CPU佔用率暴降
殺軟滿血!Intel核顯將參與安全掃描:CPU佔用率暴降
PGL單體pm使用率分析——「X&Y」噴火龍
微軟 OneDrive 用於託管惡意文件的使用率顯著上升
如何在遊戲畫面中實時顯示FPS幀數和CPU和顯卡使用率、溫度等信息
抗菌藥物規範使用後 使用率正在下降
淺談數據中心優化問題中資源利用率的預測
watchOS 5將取消時間旅行功能:因使用率低
GPU佔用率低導致遊戲卡頓?這招幫你搞定
如何查看 Linux 下 CPU、內存和交換分區的佔用率?
微軟XBOX ONE X拆解:空間利用率驚人
Apple Pay:使用率雖在上升 但用的人還是不多
逆水寒將對內存佔用率優化,8G內存的電腦應該能好好玩了吧
電腦滑鼠一直轉圈,啥程序沒開CPU使用率高、溫度高是啥原因?
怎樣知道FC等老式街機等老遊戲機在運行時,CPU和內存的佔用率?
蘋果Apple Pay的採用率不容樂觀