GPU程序緩存(GPU Program Caching)


GPU程序緩存

翻譯文章: GPU Program Caching

總覽 / 為什么

因為有一個沙盒, 每一次加載頁面, 我們都會轉化, 編譯和鏈接它的GPU着色器. 當然不是每一個頁面都需要着色器, 合成器使用了一些着色器, 這些着色器需要為tab選項卡重新渲染. 我們應該去緩存一些之前的緩存程序, 並在重新需要的時候, 直接使用他們.

我們通過一個GPU緩存完成這項緩存, 這里會使用基於內存, 或者磁盤的緩存來加速這一過程.

緩存等級

內存緩存(In-Memory Cache)

由於磁盤的訪問時間未知(以及所需要的IPC調用), 二進制中所有命中的緩存都來自內存緩存. 基於磁盤的緩存加載在啟動時進行.

內存緩存的思路關鍵

內存緩存主要存儲在GPU的通道管理器中, 所以存儲在GPU的生命周期的線程中. 因為這個原因, 我們可以假定在內存緩存的生命周期中, 相同着色器的二進制編碼不會改變(驅動程序不會變, 供應商不會變等). 所以 我們的關鍵在與沒有轉換的着色器源組成. 因為我們並想要限制秘鑰的大小, 我們只需要SHA1hash值對源進行散列.

當再次啟動一個GPU程序(這里包含兩個着色器(shaders)), 我們還需要在秘鑰(key)中, 包含一個屬性位置圖, 因為它可以影響而二進制的結果, 並對相同的着色器加以區分. 所以, 我們對兩個着色器的sha1, 做了一個SHA1的散列, 並放到了屬性圖中

磁盤緩存(Disk Cache)

磁盤緩存幫助內存緩存作為一種永久的緩存. 它擁有和內存緩存一樣的最大容量, 並且所有的程序緩存到內存緩存的時候, 也會通知內存緩存.

允許磁盤緩存命中的選項中, 包含一個鎖定GPU程序信息, 並在我們繼續執行的時候, 異步讀取二進制信息. 如果將來任何調用涉及到GPU程序, 那會一直等到異步加載完成. 然而, 因為這是一個普通的模式, 見檢查了程序的鏈接狀態后, 立即鏈接(所以, 程序是在異步執行結束后立即運行的), 讓其忽略了這個選項.

磁盤緩存的思路關鍵

因為會一直存在磁盤里面, 我們需要包含任何會影響未被轉換的着色器的二進制內容. 這包含了對驅動器和可能在chromium中轉換器的更改. 所以我們想要包括:

  • 沒有轉換的着色器源(untranslated shader sources)

  • 綁定的屬性位置圖(bound attribute location map)

  • glGetString(GL_VENDOR)

  • glGetString(GL_RENDERER)

  • 驅動器版本號(Driver Version ID)

  • 供應商標識(Vender ID)

  • Chrome Build # *(打包后的chrome??)

    那是一個GPU程序不能使用的, 只能使用在chrome項目中. 如果磁盤緩存一直在chrome中, 應該沒問題.

磁盤緩存的行文

磁盤緩存需要增加在程序啟動時的緩存能力, 才能不造成任何性能問題. 因為磁盤緩存的訪問時間未知(事實上僅僅編譯和鏈接一個程序, 比從磁盤讀取一個二進制的文件要快), 我們永遠不會使用磁盤緩存作為緩存的提供者. 相反, 我們從啟動一開始, 就加載來自內存的緩存.

為了獲得最佳的行文, 磁盤緩存需要:

  • 啟動一開始, 就加載二進制文件

    • 因為二進制大小都在1-20kb左右, 並且我們使用了IPC的方式, 所以我們不能一次性加載全部的
    • 磁盤緩存最壞的情況是, 每個文件都死空的, 所以這不應該阻塞啟動, 相反, 我們需要在一個單獨的線程上懶加載完成.
    • 應該在我們發送一個IPC之前的的時候, 進行"秘鑰兼容性"的檢查
  • 異步的方式執行緩存的更新/寫入(沒有讀取, 只通過內存緩存保持最新)

  • 瀏覽器清理緩存的時候被刪除

    實現的時候, 必須注意啟動時的競爭情況, 那里就是合成器使用着色器的地方, 這些着色器可能來磁盤, 也可能不來自磁盤, 這會導致一個問題: 我們應該能夠把一些程序標記為, 啟動時立即加載嗎? 合成器中的着色器使用的數據, 是從磁盤中獲取快, 還是進行普通的鏈接和編譯快呢?這是被認為是未來的事情, 盡管有些過頭了.

回收(Eviction)

考慮過程(Considerations)

源數據加載的時候, 頁面中最佳的回收方案是MRU. 這是因為同一份二進制文件無法使用兩次, 我們只需要加載一次.

然而, 如果考慮到我們會運行訪問不同的頁面, 這不再可行, 因為頁面的訪問不遵循資源的加載模式 背景頁會限制當前頁可用的緩存空間.

所以, 一個更好的加載方式是頁面使用LRU, 每個頁面都有MRU. 所以, 你可以從最近最少使用的頁面上的最進用過的程序上收集. 記住一點, 如果在新的tab頁中, 程序再次被貼之前的標簽, 程序會被老tab的MRU從隊列中刪除, 加入到新tab頁的MPU隊列中.

最后選擇(Final choice)

因為我們無法准確的知道GPU進程中, 我們在的選項卡/頁面上, 我們使用LRU協議進行回收. 只有我們不能將某個頁面的所有的gpu程序全部放入到緩存中的時候, 才會導致問題. 因為我們會回收第一個程序在頁面上的緩存, 重新加載后, 我們會進行重新編譯. 目前二進制的大小容易管理(加載Mini Ninjas和From Dust導致最少小6mb或者二進制), 但如果這編程了一個問題, 那之后的回收計划, 應該就像上文說述.

存儲狀態(Status Storage)

我們獲取或者保存程序二進制之前, 會做一系列的'狀態'檢查, 來避免裝換過程中/編譯過程中/鏈接過程中的着色器被緩存. 所以我們以下的狀態信息:

  • 着色器編輯(Shader Compilation)

    • SHA1(untranslated shader)*
      • 編譯狀態中(成功, 未知)
      • 引用程序的鏈接計數(Reference count of linked programs)**
  • 程序鏈接(Program Linking)

    • SHA1( SHA1(untranslated vertex shader)*** + SHA1(untranslated fragment shader) + attribute location binding map)
      • 鏈接狀態(成功, 未知)
  • *: SHA1用來避免在內存中保存, 無邊際, 未轉換的腳本.

  • **: 我們保持一個參考數值, 以便在移出着色編譯器狀態或者回收程序的時候, 有所存儲.(因為相同的一個着色器可能被用在不同的程序中)

  • ***: 鏈接的秘鑰使用的是着色器的SHA1, 所以我們可以在回收期間, 引用着色器的編譯狀態, 不需要訪問未轉換的着色器源.

二進制緩存

內存中

鏈接程序或者確定程序在緩存之后, 我們訪問內存中存儲的二進制. 存儲的秘鑰和上面程序鏈接狀態的秘鑰相同, 並且, 我們在緩存中存了下面這些值:

  • SHA1(untranslated vertes shader) (未翻譯的頂點着色器)

  • SHA1(untranslated fragment shader) (未翻譯的片段着色器)

  • vertex shader attribute to shortened name map (頂點着色器屬性被壓縮在名稱地圖中)

  • vertex shader uniform to shortened name map (片段着色器屬性被) 統一頂點着色器

  • fragment shader attribute to shortened name map(片段着色器) 片段着色器屬性壓縮到簡稱映射表中

  • fragment shader uniform to shortened name map(片段着色器統一到簡稱映射表中)

  • glGetProgramBinary的值

    • length (長度)
    • format (格式化)
    • data (數據)

    我們可以存儲哈希過后的着色器, 所以我們可以正確回收編譯狀態內存

磁盤

存儲方案還沒確定, 但是我們需要適應以下情況:

  • 存儲的所有東西是內存中二進制存儲需要的
  • 存儲的所有東西都需要創建一個對應內存的key
  • 若沒有在磁盤緩存的秘鑰對稱表中匹配到緩存, 就不去加載到內存中

直方圖(Histograms)

下面的直方圖會幫助我們調整緩存

  • 程序二進制大小 - 每一個被鏈接的二進制, 不是每一個被使用的

  • 程序緩存大小(存儲前+存儲后)

  • 二進制緩存命中的時間

  • 二進制緩存未命中鏈接時間

  • 狀態緩存命中時間

  • 狀態緩存未命中編譯時間

    注釋: 二進制緩存命中時間從8月20到22號, 產生的直方圖不正確.

緩存大小分布計算(Cache Size Distribution Calculation)

這是12/21/8普通的內存使用情況簡圖. x軸表示大小, kb, y軸表示可不短總數

直方圖

我認為隨着更多基於web的游戲出現, 存儲會繼續上升. 從Dust啟動后, 緩存的大小約3MB或者4MB.

代碼結構(Code Structure)

主要類(Main Classes)

  • 程序緩存(program_cache): 這是基礎的程序緩存類, 保存狀態信息並提供對二級制的保存/加載的虛擬方法
  • 內存程序緩存(memory_program_cache): 內存中的程序緩存, 沒有磁盤后端
  • 程序緩存lru助手(program_cache_lru_helper): 一個幫助lru策略的實用狀態類

測試

程序緩存Lru助手(ProgramCacheLruHelper)

  • 不重復使用LRU收回命令(LRU eviction order w/o reuse)
  • LRU eviction order w/ reuse(ps: 不知道w/o 表示什么)
  • 清空工作正常
  • peek/pop工作正常(集成到命令測試中)

程序緩存(ProgramCache)

  • 編譯狀態存儲, 確保key被復制
  • 編譯器不知道源代碼更改
  • 鏈接狀態存儲, 確保key被復制
  • 鏈接無法得知頂點和片段源更改
  • Eviction w/o shader reuse
  • Eviction w/ shader reuse
  • 清除工作正確

內存程序緩存(MemoryProgramCache)

  • 二進制保存時, gl正常調用, 鏈接狀態正確
  • 加載時gl正常調用, 屬性和統一映射設置正確, 二進制保存的是返回的二進制
  • 不同源不能返回同一個程序
  • 不同的屬性映射表不能返回同一個程序
  • 緩存已滿時保存進行適當回收

程序管理(ProgramManager)

  • 編譯時緩存未命中, 調用glCompile, 把狀態設置未成功
  • 在編譯器報錯的期間, 不能設置狀態
  • 編譯器狀態成功時, 不能編譯
  • 鏈接程序緩存未命中
  • 緩存未命中+鏈接的狀態下, 重用未編譯的編譯着色器
  • 正確的程序緩存設置(調用LoadLinkedProgram, 不再鏈接和再次緩存)
  • 如果正在加載緩存程序, 進行編譯和鏈接的話, 返回錯誤


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM