圖形流水線之旅 part2 GPU儲存架構和指令處理器

來源:互聯網
上載者:User
                在前面的part1中,我解釋了3D渲染指令在PC上實際被GPU處理之前所經過的各種階段,然後以指令處理器這兒挖了個坑。OK,在這部分,我們確實會先遇到指令處理器,但你要知道,所有指令緩衝區的東西都會經過儲存空間——不管是系統記憶體還是顯存。我們按順序通過流水線,所以在進入指令處理器之前,先花點時間談談儲存空間(記憶體、顯存)。 儲存空間子系統
        GPUs沒有標準規則的儲存子系統——它不像你平時看到的通用CPU或是其他硬體,因為它是為各種各樣不同使用模式設計的。有兩個根本性方式可以看出GPUs儲存子系統與一般機器的不同。        第一個就是GPU的儲存子系統非常快。一個酷睿i7 2600K在極限下可能達到19GB/s的記憶體頻寬。而一塊GeForce GTX 480的總記憶體頻寬接近180GB/s,這差不多超了一個數量級。        第二個就是GPU的儲存子系統非常慢。在Nehalem(第一代i7)上,一次快取與主存的缺失大約需要每時鐘頻率140個周期。而前面提到的GTX 480擁有400-800個時鐘的儲存訪問延遲。以周期來測算的話,GTX 480差不多有i7的4倍儲存延遲。前面提到的酷睿i7時鐘頻率為2.93GHz,而GTX 480著色時鐘頻率為1.4GHz——這又有2倍的差距了。這快接近一個數量級的差距了。        看出來了吧,GPUs可以在頻寬上可以巨量增長,但它也要承擔隨之增長的延遲。這是通用模式的一部分——GPU的輸送量受限於延遲。別在這兒蛋疼了,咱們還是做點其他的吧。        上述差不多就是你所需要瞭解的GPU儲存空間的一切。當然,除了接下來講的重要的DRAM。DRAM晶片無論是邏輯上還是物理上都是以2D網格的形式存在的。它上面擁有水平線與垂直線。每個線之間的交叉點是一個晶體管和一個電容器,如果你想瞭解的更多,去查WIKI(http://en.wikipedia.org/wiki/DRAM#Operation_principle)吧。總之,關鍵點在於,DRAM上一個位置的地址是橫、縱地址分開的,DRAM內部讀/寫通常是同時遍曆所給那一行的所有列。這意味著,訪問正確映射到DRAM上一行的儲存列比訪問相同數量的記憶體而要遍曆多行要廉價的多。這些看起來有點像DRAM的冷知識,但接下來就會變得很重要。把這個和前一段聯絡起來看,你不可能只是在記憶體中讀一點位元組就達到儲存頻寬的極限。如果你想讓儲存頻寬飽和,一次性讀DRAM上一整行吧。 PCIe主機介面
        從圖形程式員角度看,這個硬體有點沒意思。實際上,GPU硬體架構上有同樣的東西。就是它一慢就會成瓶頸,而你不得不關心的東西。所以你要做的就是讓優秀的人去看好它,確保不出事。除此之外,它讓CPU可以讀/寫訪問顯存和GPU的寄存器,讓GPU可以讀/寫訪問部分主存。其實這些都很讓人很頭疼,因為這些處理過程的延遲甚至比儲存延遲更糟——訊號離開晶片,進入插槽,經過主板,然後又回到CPU的某個地方。頻寬雖然合適——總峰值可達8GB/s的總頻寬通過16線
PCIe 2.0相連,GPU使用了大部分,所以只剩大約3到5成的總CPU記憶體頻寬可用。不像早期的標準如AGP,PCI是點對點串列連結——雙向頻寬。AGP從CPU到GPU有一個快速通道,但沒有反方向的。 最後一點關於儲存空間的零碎
        我們現在已經非常非常接近3D指令了。不過還得先解決一件小事。現在擺在我們面前的有兩種儲存空間——(局部)顯存和被映射的系統記憶體(主存)。一個花費一天向北旅行,另一個踏上PCIe大道向南旅行一星期。我們選哪條路?        早期解決方案:額外添加一條地址線來告訴你走哪條路。簡單又實用,而且已經使用多次了。或許你處於一個統一的儲存空間架構上,比如一些遊戲主機(不是PC)。那樣的話,就不用糾結選擇的問題的了,儲存空間就是你要去的終點。如果你想做的更漂亮點,可以添加一個MMU(儲存空間嵌入式管理單元),它為你提供完全虛擬化的地址空間並允許你展示各種技巧,比如將一張紋理上要頻繁被訪問的部分存在顯存上(速度快),其他部分放在系統記憶體裡,而且大部分根本不被映射——就像一團空氣。        當程式運行而又發現顯存不足時,MMU可讓你對顯存地址空間進行磁碟重組而不需要真正地拷貝東西。而且MMU也讓多進程共用一個GPU變得很容易。使用一個MMU肯定是可以的,但我不確定是否必須得有。總之,一個MMU/虛擬記憶體不是你可以實實在在摸到的(不像一個硬體架構裡的快取和儲存空間),但在一些特殊階段裡也不是很特別。        還有一個DMA引擎的東西,它可以複製記憶體而不牽扯到任何3D硬體/著色器核心。一般來說,它至少能在系統記憶體和顯存之間進行複製(兩個方向均可)。它經常做的是顯存到顯存的複製操作(你想做顯存磁碟重組的話,這個東西很有用),但不能做系統記憶體到系統記憶體的複製。因為這是GPU而不是記憶體複製單元——在CPU上做系統記憶體的複製吧,在那兒不需要雙向通過PCIe。        下面這幅圖可以說的更詳細點。如今的GPU有多個記憶體控制器(controller),每一個控制器又可以控制多個儲存體。但它們都得想方設法去擷取頻寬。        OK,總結一下,我們已在CPU上準備了一個指令緩衝區,擁有了PCIe主機介面。因此,CPU可以擷取這些並將它們的地址寫入寄存器中。我們的邏輯是由地址返回資料——如果它經由PCIe來源於系統記憶體,而我們卻想在顯存中擷取指令緩衝區,那麼KMD可設定一個DMA轉換,這樣不管是CPU還是GPU上的著色器核心都不必擔心了。然後我們可以通過儲存子系統來從顯存上的副本擷取資料。現在一切準備就緒,終於可以一窺指令的究竟了。 最後,指令處理器
        關於指令處理器的討論現在就開始了,前面講的諸多東西其實只為了一個東西:         緩衝作用        前面提過,兩條儲存路徑都會引起高頻寬而高延遲。對於GPU流水線中的接下來的大部分“位”,要做的只是運行大量獨立線程。但問題在於,我們只有一個指令處理器,它又需要按順序接收提供的指令緩衝區(因為這個緩衝區裡包含了諸如很多狀態改變與渲染的指令,這些都需要按順序正確執行)。因此,我們接下來要做的就是:添加一個足夠大的緩衝區並向前預讀足夠遠,以此來避免間斷。        在這個緩衝區裡,處理器會到達真正的指令處理前端,從根本上說,它只是一個知道如何解析指令的狀態機器。一些指令處理2D渲染操作——除非有一個單獨用於處理2D事務的指令處理器而且3D端從來沒看見它。不管怎樣,現代GPU裡仍然隱藏著一個專用2D硬體,就像某個地方存在一個VGA晶片依然支援文字模式、4-bit/pixel位面模式、平滑捲動等等。運氣不錯,沒用顯微鏡就找到了這些快要淘汰的東西。這些東西確實存在,但此後我可不會再提起。然後就是將基元(primitives)傳遞給3D/著色器管道的各種指令了。當然也有去了3    D/著色器管道卻不做任何渲染的指令。這些我會在下一章詳細介紹。        接著是一些改變狀態的指令。作為一個程式員,你可以想成是改變變數。GPU是一個做海量並行的計算機,你不能在一個並行系統裡改變一個全域變數並期望其他一切工作正常——如果你沒法保證在你強制改變後一切OK的話,那最後肯定會有BUG。其實有許多流行的解決方案,基本上所有的晶片都會根據不同的狀態類型使用不同的方法。
無論何時改變一個狀態,你得強制命令所有與該狀態有關的未完成工作立馬結束/完成(比如基本地局部流水線Flush操作)。曾經,圖形晶片處理大部分狀態更改都是採用此法——在批處理少、三角形少、流水線短的情況下簡單而廉價。當然啦,隨著批處理和三角形數量增加,流水線變長,這種方法的消耗會迅速增長。現在這種方法還在用,但僅限於狀態更改頻率不高或者是一些昂貴/困難的特殊方案。
  • 你可以讓硬體單元完全無狀態。只需要將狀態更改指令傳遞給需要的狀態,然後每個周期將該狀態附加給所有下遊的目前狀態。已更改的狀態只迴圈不儲存。它們穿過一個流水線階段,然後奔向下一個。因此,如果你的狀態裡只有很少的位元據(狀態更改的不多),而一些流水線階段要查看該狀態內的位元據,那這種操作相當昂貴而且不實用。當然,如果是設定整個活躍紋理的紋理採樣狀態,到還行。
  • 有時,只儲存了狀態的一個副本,而階段更改序列化事務很頻繁,每次更改你都得將副本Flush一次,但如果你有兩個副本,事情就會好很多。這樣你的狀態設定前端就可以提前擷取資訊。假定你有足夠的寄存器(插槽)儲存每個狀態的兩種模式,有效模式設定為引用插槽0。你可以安全地修改成插槽1而不需要停止或幹擾作業。現在你不必發送全部狀態迴圈流水線——只需一個選擇使用插槽0還是1的單位元指令。當然,如果狀態更改指令進來時,插槽0和1都不空,那你還是等等會,但你可以提前一步擷取到這個情況。使用更多插槽用的是相同的技術。
  • 對於一些像採樣器或者著色器資源檢視的狀態,你需要同時設定大量狀態,問題在於你做不了。你不會希望只因為可能會使用跟蹤到的2條飛行狀態集而預留2*128活躍紋理的狀態空間。對於這些情況,你可以使用一種寄存器重新命名方案——一個含有128個物理紋理描述符的記憶體池。除非一個著色程式中確實同時需要128個紋理,那狀態更改將會非常慢。但一般來說,一個APP用不到20個紋理,你就有足夠的動態餘量來維持多個狀態版本的運行。
        這個清單不是很全面——但關鍵點在於一些看起來像更改變數一樣簡單的事情可能需要不一般的海量硬體支援。 同步
        最後,剩下的指令集處理CPU/GPU和GPU/CPU之間的同步性問題。        通常,講解這些的形式都是“如果X發生了,執行Y”。我會先解決“執行Y”部分——關於Y是什麼,這兒有兩種不錯的觀點:它可能是GPU大喊CPU立刻做一些事的push-model通知(“喂,CPU!我現在要在垂直空白間隙進入0顯示模式,所以如果你要光滑地翻轉緩衝的話,就是現在!”),或者,它可能是GPU先記下某事,CPU可以延後詢問的pull-model事務(“那個,GPU啊,你處理的最近的指令緩衝片段是啥?“—“我查一下啊……是序列ID
303”)。前者一般由中斷操作來實施,而且只用於很少的、高優先順序的情況,因為中斷操作太昂貴了。所有後者的實施,都是在必鬚髮生時才從指令緩衝區向CPU可見的GPU寄存器內寫入資料。        假定你有16個寄存器。然後給寄存器0分配currentCommandBufferSeqId。先給要提交給GPU的每一個指令緩衝區分配一個序號,然後在每個指令緩衝區開始位置添加提示“如果你要在指令緩衝區內擷取該指標,請寫入寄存器0”.好的,現在我們就知道GPU當前正在處理哪一個指令緩衝區了。而且我們還知道,指令處理器是嚴格按順序實施/結束指令的。所以如果指令緩衝區303中的第一條指令被執行了,那麼序列ID
302之前(包括302)的所有指令緩衝區都已經實施/結束了,可以被KMD回收了,要麼釋放,要麼修改。        我們現在有了一個關於X的例子:“如果你到了這兒”——這可能是最簡單的例子,但已經夠用了。還有其他例子:“如果所有著色器在指標進入指令緩衝區之前都已經完成從(該指標指向的)批處理中讀入所有紋理的工作”(這標誌回收指向紋理/渲染目標記憶體的指標很安全),“如果已完成渲染所有活躍渲染目標/UAV”,還有“如果所有操作到這點全部完成”等等等等。        有多種方法可以取出寫入到狀態寄存器中的值,但我覺得唯一正常的方法是使用一個順序計數器。我這兒沒講那些與原理無關的隨機資訊(什麼是順序計數器?),因為我覺得你應該瞭解。        到這兒我們已經說了一半——可以從GPU返回狀態給CPU,允許在驅動程式上做正確的儲存管理(尤其是我們可以查詢那些曾用於頂點緩衝、指令緩衝、紋理和其他資源的已被安全回收了的記憶體)。但這不是全部——我們漏掉了一點東西。例如,如果我們要純粹在GPU端做同步怎麼辦?先回到渲染目標的例子上。我們在渲染結束前是不能將它作為紋理的。解決辦法就是——“等”:等到寄存器M有了值N。相較而言,花費可能相等,可能便宜,可能昂貴——我一般簡單地認為是相等的。該方法允許我們在提交一個批處理之前做渲染目標同步。它也允許我們做一次全域GPU
flush操作。一切搞定。GPU/GPU的同步問題終於解決了——在介紹DX11中計算著色器的時候,有一種更好的——粒度同步(grained synchronization),通常這是你在GPU端唯一的同步機制。對於週期性渲染情況,你需要關心更多。        如果你要從CPU端寫入寄存器,也可以使用另一種方法——提交一個包含“等”特定值操作的局部指令緩衝,然後由CPU而不是GPU更改寄存器。這種事可以由D3D11風格的多線程渲染來實施,你可以提交一個引用鎖定在CPU端的頂點/索引緩衝的批處理。你只需在實際渲染調用的前方等著就行。一旦頂點/索引緩衝解鎖,CPU就能更改寄存器中的內容。如果GPU從未在得到這樣的指令緩衝,“等”就成了一個空操作。如果得到了,就會花費一些(指令處理器)時間來解析,直到確認資料存在。事實上,即使沒有CPU可寫狀態寄存器,如果你可以在提交之後修改指令緩衝的話,也可做相同的事,只要有一個指令緩衝“跳轉”操作。        當然,其實你不需要這種註冊寄存器/等待寄存器模型。對於GPU/GPU同步,你只需簡單地擁有一個“rendertarget barrier”結構來確保一個渲染目標(render target)安全可用,再加上一條“flush everything”指令即可。但我自己更喜歡這種註冊寄存器風格的模型,因為它可以一石二鳥(報告CPU正使用的資源和GPU自動同步)。        下面這個圖表有點複雜,我簡單的說下。基本思想是:指令處理器在前面有一個FIFO,接著是指令解碼邏輯,然後通過與2D單元、3D前端(標準3D渲染)、著色器單元(計算著色器)直接交流的各種塊上面執行,接著是一個處理同步/等待指令的塊,還有一個處理指示緩衝跳轉/調用的單元。所有這些分配了工作的單元都需要向我們發送返回完成事件,這樣我們才知道比如什麼時候紋理沒被使用,它們的記憶體可被回收。 結束語        下一節我們會開始接觸一些實際的渲染工作。事實上,現今流水線已經開始出現分支,如果我們運行計算著色器,那下一步將是運行計算著色程式。但我們不這麼做,因為計算著色器是很後面的部分的主題。還是先從標準(固定)渲染流水線開始吧。 提一句:我在這兒說的只是大致架構,我為了便於理解少講了很多細節,有必要(興趣)的可以自己去研究。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.