【技術(shù)美術(shù)百人計劃】GPU硬件架構(gòu)及運(yùn)行機(jī)制
提問:
GPU是如何與CPU協(xié)調(diào)工作的?
GPU也有緩存機(jī)制嗎?有幾層?速度差異是多少?
GPU渲染流程有哪些階段?他們的功能分別是什么?
Early-Z技術(shù)是什么?發(fā)生在哪個階段?這個階段還會發(fā)生什么?
SIMD和SIMT是什么?他們的好處是什么?co-issue呢?
GPU是并行處理的嗎?硬件層是如何設(shè)計并實(shí)現(xiàn)的呢?
GPC、TPC、SM是什么?Warp又是什么?它們和Core、Thread之間的關(guān)系如何?
頂點(diǎn)著色器和像素著色器可以是同一處理單元嗎?為什么?
像素著色器最小處理單位是一像素嗎?為什么?會帶來什么影響?
Shader中if、for會降低渲染效率嗎?為什么?
渲染相同面積的圖形,三角形的數(shù)量會影響效率嗎?為什么?
GPU Content是什么?有什么作用?
造成渲染瓶頸的問題可能有哪些?該如何避免或優(yōu)化它們?
GPU是什么
GPU(Graphics Processing Unit)是圖形處理單元,是專門用于繪制圖像和處理單元數(shù)據(jù)的特定芯片。GPU不是顯卡,是顯卡上最核心的部件。
GPU物理架構(gòu)
由于納米工藝的引進(jìn),GPU可以將數(shù)以億計的晶體管和電子器件集成于芯片內(nèi)。當(dāng)GPU與散熱風(fēng)扇、PCI插槽、HDMI等部件組成后,就成為了顯卡。
顯卡不能獨(dú)立工作,需要裝載在主板上,結(jié)合CPU、內(nèi)存、顯存、顯示器等硬件設(shè)備,組合成完整的PC。
GPU微觀物理結(jié)構(gòu)
NVidiaTesia架構(gòu):
擁有7組TPC(Texture/Processor Cluster,紋理處理簇)
每個TPC有兩SM(Streaming Multiprocessor,流多處理器)
每個SM包含8個SP(StreamingProcessor,流處理器)
2個SFU(Special Function Unit,特殊函數(shù)單元)
L1緩存、MT Issue(多線程指令獲取)、C-Cache(常量緩存)、共享內(nèi)存
除了TPC核心單元,還有與顯存、CPU、系統(tǒng)內(nèi)存交互的各種部件。

NVidiaFermi架構(gòu):
有16個SM(Streaming Multiprocessor,流多處理器)
兩個WarpScheduler(線程束)
兩組共32個Core
16組加載存儲單元(LD/ST)
4個特殊函數(shù)單元(SFU)
分發(fā)單元(Dispatch Unit)
每個Core有一個FPC(浮點(diǎn)數(shù)單元)、一個ALU(邏輯運(yùn)算單元)

NVidiaMaxwell架構(gòu)
采用了Maxwell的GM204,擁有4個GPC
每個GPC有4個SM,對比Tesia架構(gòu)來說在處理單元上有了很大提升

NVidiaTuring架構(gòu)
6個GPC(圖形處理簇)
36個TPC(紋理處理簇)
72個SM(流多處理器)
每個GPC上有6個TPC、每個TPC上有兩個SM
4608個CUDA核
72個RT
576個Tensor核
288個紋理單元
12x32位GDDR6內(nèi)存控制器(共384位)
每個SM包含64個CUDA核(CUDA是NVIDIA推出的統(tǒng)一計算架構(gòu))
每個SM包含8個Tensor核(Tensor Core是專為執(zhí)行張量或矩陣運(yùn)算而設(shè)計的專用執(zhí)行單元)
每個SM包含256kb的寄存器文件


GPU架構(gòu)的共性
縱觀所有GPU架構(gòu),存在著很多相同概念的部件
GPC(圖形處理簇)
TPC(紋理處理簇)
Thread(線程)
SM、SMX、SMM(StreamMultiprocesser,流多處理器)
Warp線程束、WarpScheduler(Warp編排器)
SP(StreamProcessor,流處理器)
Core(執(zhí)行數(shù)學(xué)運(yùn)算的核心)
ALU(邏輯運(yùn)算符單元)
FPU(浮點(diǎn)數(shù)單元)
SFU(特殊處理單元)
ROP(RenderOutputUnit,渲染輸入單元)
Load/StoreUnit(加載存儲單元)
L1Cache(L1緩存)
L2Cache(L2緩存)
SharedMemory(共享內(nèi)存)
RegisterFile(寄存器)
GPU是天然并行的,現(xiàn)代GPU的架構(gòu)是以高度并行能力設(shè)計的
GPU微觀物理結(jié)構(gòu)
包含關(guān)系:GPC==>TPC==>SM==>CORD
SM包含PolyMorphEngine(多邊形引擎)、L1Cache(L1緩存)、SharedMemory(共享內(nèi)存)、Core(執(zhí)行數(shù)學(xué)運(yùn)算的核心)等。
CORE又包含ALU、FPU、ExecutionContent(執(zhí)行上下文)、Detch、解碼(Decode)

GPU渲染總覽
Fermi架構(gòu)運(yùn)行機(jī)制總覽圖:
從Fremi開始NVIDIA使用類似的原理架構(gòu),使用一個GigaThreadEngine來管理所有正在運(yùn)行的工作,GPU被劃分為多個GPCs(GraphicProcessingCluster),每個GPC擁有多個SM(SMX、SMM)和一個光柵化引擎(RasterEngine),它們其中有很多的連接,最顯著的是Crossbar,他可以連接GPCs和其他功能性模塊(例如ROP和其他子系統(tǒng))
程序員編寫的Shader是在SM上完成的,每個SM包含許多為線程執(zhí)行數(shù)學(xué)運(yùn)算的Core(核心)。例如:一個線程可以是頂點(diǎn)或像素著色器調(diào)用。這些Core和其他單元由WarpScheduler驅(qū)動,WarpScheduler管理一組32個線程作為Warp(線程束)并將要執(zhí)行的指令移交給DispatchUnits

GPU邏輯管線
以Fermi家族的SM為例子,進(jìn)行說明:

1、程序通過圖形API(DirectX、Glsl、WebGL)發(fā)出drawcall指令,指令被推送到驅(qū)動程序,驅(qū)動程序檢查指令合法性,然后將指令放到GPU可讀的PushBuffer中。
2、經(jīng)過一段時間或顯示調(diào)用flush指令后,驅(qū)動程序把PushBuffer的內(nèi)容發(fā)送給GPU,GPU通過主機(jī)接口(HostInterface)接受命令,并通過前端(FrontEnd)處理這些命令。
3、在圖元分配器(PrimitiveDistributor)中開始工作分配,處理IndexBuffer中的頂點(diǎn)產(chǎn)生三角形分成批次(batches),然后發(fā)送給多個GPCs。這一步理解就是提交上來n個三角形,分配給這幾個GPC同時處理。

4、在GPC中,每個SM中的PolyMorphEngine負(fù)責(zé)通過三角形索引(triangleIndices)取出三角形的數(shù)據(jù)(vertexData),即圖中的VertexFetch模塊。
5、取出數(shù)據(jù)后,在SM中以32個線程為1組的線程束(Warp)來調(diào)度,來開始處理頂點(diǎn)數(shù)據(jù)
6、SM的Warp調(diào)度器會按照順序分發(fā)指令給整個Warp,單個Warp中的線程會鎖步(lock-step)執(zhí)行各自的指令,如果線程碰到不激活執(zhí)行的情況也會被遮掩(be masked out)
7、Warp指令可一次完成,也可被多次調(diào)度,例如通常SM中的LD/ST(加載存?。﹩卧獢?shù)量明顯少于基礎(chǔ)數(shù)學(xué)操作單元
8、由于某些指令比其他指令需要更長時間來完成,特別是內(nèi)存加載,warp調(diào)度器可能會簡單的切換到另一個沒有內(nèi)存等待的Warp,這是GPU如何克服內(nèi)存讀取延遲的關(guān)鍵,只是簡單的切換活動線程組。

9、一旦被Warp完成了Vertex-Shader的所有指令,運(yùn)算結(jié)果會被ViewportTransform模塊處理,三角形會被裁剪,然后準(zhǔn)備柵格化,GPU會使用L1和L2緩存來進(jìn)行Vertex-Shader和Pixel-Shader的數(shù)據(jù)通信。
10、接下來這些三角形將會被分割,再分配給多個GPC,三角形的范圍決定了他將被分配到哪個光柵化引擎(rasterEngines),每個RasterEngines覆蓋了多個屏幕上的Tile,這等于把三角形的渲染分配到了多個Tile上面。也就是像素階段就把按三角形劃分變成了按顯示像素劃分了。

11、SM上的AttributeSetup保證了從Vertex-Shader來的數(shù)據(jù)經(jīng)過插值后是Pixel-Shader可讀的。
12、GPC上的光柵引擎(RasterEngines)在他接收到的三角形上工作,來負(fù)責(zé)這些三角形的像素信息生成,同時會處理背面剔除和Early-Z剔除。
13、32個像素線程將被分為1組,或者說8個2x2的像素塊,這是在像素著色器上面的最小工作單元,在這個像素線程內(nèi),如果沒有被三角形覆蓋就會被遮掩,SM的waro調(diào)度器會管理像素著色器的任務(wù)
14、接下來的階段就和Vertex_Shader中的邏輯步驟完全一致,但是變成了在像素著色器線程中執(zhí)行。由于不耗費(fèi)任何性能就能獲取一個像素內(nèi)的值,導(dǎo)致鎖步執(zhí)行非常便利,所有的線程可以保證所有的指令可以在同一點(diǎn)。

15、像素著色器已經(jīng)完成了顏色的計算和深度值的計算,在這個點(diǎn)上,我們必須考慮三角形的原始API順序,然后才將數(shù)據(jù)移交給ROP(RenderOutputUnit,渲染輸入單元)一個ROP內(nèi)部有很多ROP單元,在ROP單元中處理深度測試,和FrameBuffer的混合,深度和顏色的設(shè)置必須是原子操作,否則兩個不同的三角形在同一個像素點(diǎn)就會有沖突和錯誤
Early-Z
早期GPU的渲染管線的深度測試是在像素著色器之后才執(zhí)行,這樣會造成很多本不可見的像素執(zhí)行了耗性能的計算。后來,為了減少像素著色器的額外消耗,將深度測試提前到像素著色器之前,這就是Early-Z技術(shù)的由來。Early-Z技術(shù)可以將很多無效的像素提前剔除,避免它們進(jìn)入耗時嚴(yán)重的像素著色器。
Early-Z剔除的最小單位不是1像素,而是像素塊(2x2)

但是,以下情況會導(dǎo)致Early-Z失效:
1、開啟AlphaTest:由于AlphaTest需要在像素著色器后面的AlphaTest階段作比較(DirectX的discard,OpenGL的clip),所以無法在像素著色器之前決定該像素是否被剔除。
2、開啟AlphaBlend:啟用了Alpha混合的像素很多需要與FrameBuffer做混合,無法執(zhí)行深度測試,也就無法利用Early-Z技術(shù)。
3、關(guān)閉深度測試:Early-Z是建立在深度測試開啟的條件下,關(guān)閉深度測試,也就無法使用Early-Z技術(shù)
4、開啟Multi-Samping:多采樣會影響周邊像素,而Early-Z階段無法得知周邊像素是否被裁剪,故無法提前剔除
5、其他任何導(dǎo)致需要混合后面顏色的操作。
SIMD和SIMT
SIMD(Single Instruction Multiple Data)是單指令多數(shù)據(jù),在GPU的ALU單元內(nèi)一條指令可以處理多維向量(一般是4D)的數(shù)據(jù)。比如,有以下shader指令:
float4 c = a + b;//ab都是float4類型
對于沒有SIMD的處理單元,需要4條指令將4個float類型相加。
但有了SIMD技術(shù),只需要一條指令便可完成。

SIMT(Single Instruction Multiple Threads,單指令多線程)是SIMD的升級版,可對GPU中單個SM中的多個Core同時處理同一指令,并且每個Core存取的數(shù)據(jù)可以是不同的。
SIMT_ADD c,a,b
上述指令會被同時送入在單個SM中被編組的所有Core中,同時執(zhí)行運(yùn)算,但a,b,c的值可以不一樣。

co-issue
co-issue是為了解決SIMD運(yùn)算單元無法充分利用的問題,由于float數(shù)量的不同,ALU的利用率從100依次下降到75、50、25。
為了解決著色器在低維向量利用率低的問題,可以通過合并1D與3D或2D與2D的指令。
但是,對于向量運(yùn)算單元(VectorALU)如果其中一個變量既是操作數(shù)又是存儲數(shù)的情況,無法啟用co-issue技術(shù)

CPU與GPU對比
CPU是一個具有多種功能的優(yōu)秀領(lǐng)導(dǎo)者。他的優(yōu)點(diǎn)在于調(diào)度、管理、協(xié)調(diào)能力強(qiáng),但是計算能力一般
GPU相當(dāng)于一個接受CPU調(diào)度“擁有大量計算能力”的員工。


CPU-GPU異構(gòu)系統(tǒng)

根據(jù)CPU與GPU是否共享內(nèi)存,可分為兩種類型的CPU-GPU架構(gòu)
分離式架構(gòu):CPU和GPU各有獨(dú)立緩存和內(nèi)存,它們通過PCI-e等總線通訊。這種結(jié)構(gòu)的缺點(diǎn)在于PCI-e相對于兩者具有低帶寬和高延遲,數(shù)據(jù)的傳輸成為了其中的性能瓶頸。使用廣泛,如PC等。
耦合式架構(gòu):CPU和GPU共享內(nèi)存和緩存。AMD的APU采用的就是這種結(jié)構(gòu),主要應(yīng)用于游戲主機(jī)中,如PS4,智能手機(jī)等。
在存儲管理方面,分離式結(jié)構(gòu)中CPU和GPU各自擁有獨(dú)立的內(nèi)存,二者共享一套虛擬地址空間,必要時會進(jìn)行內(nèi)存拷貝。耦合式結(jié)構(gòu)中,GPU沒有獨(dú)立內(nèi)存,與CPU共享系統(tǒng)內(nèi)存,由MMU進(jìn)行存儲管理。
GPU資源機(jī)制

內(nèi)存架構(gòu):
GPU與CPU類似,也有多級緩存結(jié)構(gòu):寄存器、L1緩存、L2緩存、GPU顯存、系統(tǒng)顯存
他們的存取速度從寄存器到系統(tǒng)內(nèi)存依次變慢。
由此可見,shader直接訪問寄存器,L1L2緩存還是比較快的,但訪問紋理、常量緩存和全局內(nèi)存非常慢,會造成很高的延遲。
GPU內(nèi)存分布在Ram存儲芯片或者GPU芯片上,它們物理上所在的位置,決定了他們的速度、大小以及訪問規(guī)則。
全局內(nèi)存(Global memory)位于片外存儲體中,容量大、訪問延遲高、傳輸速度較慢、使用二級緩存(L2 cache)做緩沖
本地內(nèi)存(Local memory)一般位于片內(nèi)存儲體中,變量、數(shù)組、結(jié)構(gòu)體等都存放在此處,但是有大數(shù)組、大結(jié)構(gòu)體以至于寄存器區(qū)放不下它們,編譯器在編譯階段就會將它們放到片外的DDR芯片中(最好的情況也是放于L2 cache),且將它們標(biāo)記為Local。
共享內(nèi)存(Shared memory)位于每個流處理器組中(SM)中,訪問速度僅次于寄存器
寄存器內(nèi)存(Register memory)位于每個流處理器組中(SM)中,訪問速度最快的存儲體,用于存放線程執(zhí)行時所需要的變量。
常量內(nèi)存(Constant memory)位于每個流處理器組中(SM)中和片外的RAM存儲器中。
紋理內(nèi)存(Constant memory)位于每個流處理器組中(SM)中和片外的RAM存儲器中。
GPU資源管理模型

CPU-GPU數(shù)據(jù)流

將主存的處理數(shù)據(jù)復(fù)制到顯存中
CPU指令驅(qū)動GPU
GPU中每個運(yùn)算單元并行處理,此步會從顯存存取數(shù)據(jù)
GPU將顯存結(jié)果傳回主存
Shader運(yùn)行機(jī)制
在執(zhí)行階段,CPU將Shader二進(jìn)制指令經(jīng)由PCI-e推送到GPU端。GPU在執(zhí)行代碼時,會用Content將指令分成若干Channel推送到各個Core的存儲空間。

對于SIMT架構(gòu)的GPU,匯編指令有所不同,變成了SIMT特定指令代碼
并且Context以Core為單位組成共享的結(jié)構(gòu),同一個Core的多個ALU共享一組Context
如果有多個Core,就會有更多的ALU同時參與Shader計算, 每個Core執(zhí)行的數(shù)據(jù)是不一樣的,可能是頂點(diǎn)、圖元、像素等任何數(shù)據(jù)。

GPU Content和延遲
由于SIMT技術(shù)的引入,導(dǎo)致很多同一個SM內(nèi)的很多Core并不是獨(dú)立的,當(dāng)它們當(dāng)中有部分Core需要訪問到紋理、常量緩存和全局內(nèi)存時,就會導(dǎo)致非常大的卡頓(Stall)
如圖:有四種上下文(Content)它們共用一組運(yùn)算單元ALU

假設(shè)第一組Context需要訪問緩存或內(nèi)存,會導(dǎo)致2-3周期的延遲,此時調(diào)度器會激活第二組Content以利用ALU
當(dāng)?shù)诙M卡住又會依次激活3-4組Content了,直到第一組Content恢復(fù)運(yùn)行或所有都被激活。
延遲的后果是每組Content總體執(zhí)行時間被拉長了
越多Content可用就越可以提升運(yùn)算單元吞吐量。
總結(jié):
頂點(diǎn)著色器和像素著色器都是在同一單元中執(zhí)行的(在原來的架構(gòu)中vs和ps的確是分開的,后來nv把這個統(tǒng)一了)vs是按照三角形來處理的,ps是按照像素來并行處理的
vs和ps中數(shù)據(jù)是通過L1和L2緩存?zhèn)鬟f的
warp和thread都是邏輯上的概念,sm和sp都是物理上的概念,線程數(shù) != 流處理器數(shù)
盡量使用自己拓展的幾何實(shí)例化替代Unity提供的靜態(tài)合批、動態(tài)合批、前者將合并mesh增加vbo的內(nèi)存占用,后者則會增加cpu端的耗時開銷
盡量減少頂點(diǎn)數(shù)與三角面數(shù),前者減少頂點(diǎn)著色器的運(yùn)算,減少GPU顯存中FrameData的內(nèi)存存儲,后者減少片元著色器的消耗
避免每幀提交Buffer數(shù)據(jù),比如Unity的CPU版本的粒子系統(tǒng),可以使用GPU版本的粒子系統(tǒng),將修改數(shù)據(jù)移動到GPU,避免大片的透明粒子特效,會造成嚴(yán)重的Overdraw
減少渲染狀態(tài)的設(shè)置與獲取,如在Update獲取設(shè)置Shader的屬性或者公共變量。CPU是通過MMIO獲取寄存器數(shù)據(jù),這將耗費(fèi)更多的時間周期
3D物體盡量使用LOD處理頂點(diǎn)與面數(shù)的消耗,開啟Mipmap減少貼圖緩存命中的丟失
避免AlphaTest的使用,會造成Early-Z失效
避免三角面過小,會加劇過度繪制的情況,也就是前面提到的三角形只占3個像素點(diǎn),卻使用了12個線程去計算像素值然后屏蔽其余9個計算結(jié)果
在寄存器數(shù)量與變體中尋找平衡,使用if變量達(dá)成靜態(tài)分支,取代變體。一方面可以減少變體數(shù)量,另一方面也可以使URP中的SRP Batch更高效合批
盡量避免動態(tài)判斷分支也就是Shader中的if true和false都會走的情況
減少復(fù)雜函數(shù)的調(diào)用,從硬件架構(gòu)上就可以看出特殊函數(shù)處理單元是遠(yuǎn)遠(yuǎn)小于正常計算的單元的

Gefore RTX 2060驗(yàn)證


