GPU體系架構(二):GPU儲存體系

DeepDream發表於2019-06-14

GPU是一個外圍裝置,本來是專門作為圖形渲染使用的,但是隨著其功能的越來越強大,GPU也逐漸成為繼CPU之後的又一計算核心。但不同於CPU的架構設計,GPU的架構從一開始就更傾向於圖形渲染和大規模資料的平行計算處理。而大規模的平行計算,離不開大規模的資料傳輸,只有深入瞭解了GPU的儲存體系,才能真正發揮GPU的威力,寫出高效能的軟體產品。但是由於GPU儲存體系相關的資料非常少,加之非常分散,所以在看了大量的零散資料後,想通過這篇文章,總結一下關於GPU儲存相關的知識點,以期達到加深理解的目的。

 

GPU儲存體系的設計哲學是更大的記憶體頻寬,而不是更低的訪問延遲。該設計原則不同於CPU依賴多級Cache來降低記憶體訪問延遲的策略,GPU則是通過大量的並行執行緒來規避或者叫隱藏記憶體訪問的延遲,具體來說就是GPU在等待某個記憶體資料到來的時候,會執行成百上千個其他與該資料無關的執行緒,來處理另外的資料請求,這就是GPU儲存體系記憶體訪問的特點:高頻寬,高延遲。

 

正式開始之前,我們需要了解幾個基礎的概念。我們通常在講記憶體時,多數情況下都是指CPU的專用儲存,從GPU儲存的角度來說,CPU的記憶體一般稱之為主存(main memory),GPU自己的儲存則稱為local memory,即GPU的本地儲存,有時候也稱為video memory。

 

GPU的儲存體系根據GPU的型別不同,可以是邏輯上的,也可以是物理上的。對於整合顯示卡(即integrated GPU)而言,例如 Intel HD Graphics ,GPU和CPU位於同一die中,所以它沒有自己的物理儲存裝置,而是共享CPU的儲存空間,即Unified Memory Architecture(一致性儲存架構),通常是從CPU的儲存中劃分一部分出來作為該GPU的local memory;另一種顯示卡稱為獨立顯示卡(即dedicated GPU),像Nvidia和AMD生產的GPU就屬於這類,它們都擁有自己的物理儲存裝置,是我們日常使用最多的GPU型別了。無論哪種GPU,它都擁有自己的一套地址空間,且獨立於CPU的虛擬記憶體地址空間。GPU地址空間的管理是通過核心態驅動來完成的,例如Windows上的KMD(Kernel-Mode Driver)

 

對於integrated gpu而言,因為GPU和CPU處於同一die中,所以GPU和CPU很多時候是共享匯流排的。除了GPU自己的local memory之外,CPU和GPU之間有時候需要共享一些資料,例如在渲染的時候,CPU將頂點資料放入主存當中,供GPU使用。由於主存的內容對GPU來說是不可見的,所以GPU是不能直接訪問這些資料的。為了讓GPU訪問CPU主存的內容,業界引入了一個叫GART(即Graphic Address Remapping Table)的技術。GART是一個 I/O memory management unit (IOMMU) ,說白了就是一個記憶體地址的對映表,可以將CPU的記憶體地址重新對映到GPU的地址空間,這樣就可以讓顯示卡直接訪問(DMA,direct memory access)host system memory。

 

反過來,CPU如何訪問GPU的儲存空間呢?因為integrated gpu的儲存空間是從主存分出的一部分,一般情況下都比較小,OS可以將GPU的整個儲存空間對映到CPU的地址空間。但是對於dedicated gpu來說,這種方式就不行了,因為獨立顯示卡的視訊記憶體一般比較大,一個32位的OS整個地址空間也才4GB。所以獨立顯示卡擁有與integrated gpu不同的地址空間對映機制,用於解決這個問題。一種比較常用的方式是對映一部分GPU儲存空間到CPU的地址空間,典型大小為256MB/512MB,這段地址空間會通過PCIe的bar獲取一個CPU可見的地址空間。最新的PCIe支援 resize bar技術,支援該技術的GPU可以動態調整對映區域的大小。

 

簡單介紹完GPU的儲存體系後,我以OpenGL程式為例,來分析一下OpenGL中資料的upload和download過程,從而瞭解GPU儲存體系在實際程式中的運用。

 

OpenGL更新資料的常用函式家族是:glBuffer*Data和glMapBuffer*。更準確的說是CPU需要更新資料給GPU使用時,頂點資料的更新,紋理資料的上傳等等,需要CPU到GPU的資料傳輸,這個過程稱為streaming。這個資料傳輸的過程有兩種方式:

l  glBufferData/glBufferSubData

通過這兩個函式,可以將資料從main memory拷貝到pinned memory,一旦拷貝完成,就會發起一次非同步的DMA(Direct Memory Access)傳輸,將資料傳輸給GPU,然後就會從函式呼叫返回,一旦函式返回,你就可以對原來CPU主存中的資料做任何處理,修改或者刪除。

l  glMap*/glUnmap*

通過mapping的方式傳輸資料,你可以獲取一個指向pinned memory的指標,通過該指標你可以拷貝main memory上的資料到pinned memory,然後呼叫glUnmap通知driver你已經完成資料的更新,這種方式看似跟上面的glBufferData/glBufferSubData一樣,但是你可以獲得更多的控制權。

 

該例子中的pinned memory就是CPU記憶體上的一塊專門用於GART的儲存區域,DMA傳輸則是通過上文提到的PCIe bar來實現的,瞭解了GPU的儲存體系,在使用圖形API進行渲染繪製時,才能清晰的瞭解資料的歸屬和流向,從而避免不必要的錯誤和效能損失。

 

 

 

 

 

 

 

 

參考連結:

https://www.makeuseof.com/tag/can-shared-graphics-finally-compete-with-a-dedicated-graphics-card/

https://lwn.net/Articles/257417/

https://zhuanlan.zhihu.com/p/35891701

https://fgiesen.wordpress.com/2011/07/01/a-trip-through-the-graphics-pipeline-2011-part-1/

相關文章