GPU是一個外圍設備,原本是專門做爲圖形渲染使用的,可是隨着其功能的愈來愈強大,GPU也逐漸成爲繼CPU以後的又一計算核心。但不一樣於CPU的架構設計,GPU的架構從一開始就更傾向於圖形渲染和大規模數據的並行計算處理。而大規模的並行計算,離不開大規模的數據傳輸,只有深刻了解了GPU的存儲體系,才能真正發揮GPU的威力,寫出高性能的軟件產品。可是因爲GPU存儲體系相關的資料很是少,加之很是分散,因此在看了大量的零散資料後,想經過這篇文章,總結一下關於GPU存儲相關的知識點,以期達到加深理解的目的。架構
GPU存儲體系的設計哲學是更大的內存帶寬,而不是更低的訪問延遲。該設計原則不一樣於CPU依賴多級Cache來下降內存訪問延遲的策略,GPU則是經過大量的並行線程來規避或者叫隱藏內存訪問的延遲,具體來講就是GPU在等待某個內存數據到來的時候,會運行成百上千個其餘與該數據無關的線程,來處理另外的數據請求,這就是GPU存儲體系內存訪問的特色:高帶寬,高延遲。app
正式開始以前,咱們須要瞭解幾個基礎的概念。咱們一般在講內存時,多數狀況下都是指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)ide
對於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。wordpress
反過來,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。這個數據傳輸的過程有兩種方式:spa
l glBufferData/glBufferSubData.net
經過這兩個函數,能夠將數據從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/