GPU程序緩存(GPU Program Caching)

GPU程序緩存

翻譯文章: GPU Program Cachingweb

總覽 / 爲何

由於有一個沙盒, 每一次加載頁面, 咱們都會轉化, 編譯和連接它的GPU着色器. 固然不是每個頁面都須要着色器, 合成器使用了一些着色器, 這些着色器須要爲tab選項卡從新渲染. 咱們應該去緩存一些以前的緩存程序, 並在從新須要的時候, 直接使用他們.chrome

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

緩存等級

內存緩存(In-Memory Cache)

因爲磁盤的訪問時間未知(以及所須要的IPC調用), 二進制中全部命中的緩存都來自內存緩存. 基於磁盤的緩存加載在啓動時進行.後端

內存緩存的思路關鍵

內存緩存主要存儲在GPU的通道管理器中, 因此存儲在GPU的生命週期的線程中. 由於這個緣由, 咱們能夠假定在內存緩存的生命週期中, 相同着色器的二進制編碼不會改變(驅動程序不會變, 供應商不會變等). 因此 咱們的關鍵在與沒有轉換的着色器源組成. 由於咱們並想要限制祕鑰的大小, 咱們只須要SHA1hash值對源進行散列.瀏覽器

當再次啓動一個GPU程序(這裏包含兩個着色器(shaders)), 咱們還須要在祕鑰(key)中, 包含一個屬性位置圖, 由於它能夠影響而二進制的結果, 並對相同的着色器加以區分. 因此, 咱們對兩個着色器的sha1, 作了一個SHA1的散列, 並放到了屬性圖中緩存

磁盤緩存(Disk Cache)

磁盤緩存幫助內存緩存做爲一種永久的緩存. 它擁有和內存緩存同樣的最大容量, 而且全部的程序緩存到內存緩存的時候, 也會通知內存緩存.異步

容許磁盤緩存命中的選項中, 包含一個鎖定GPU程序信息, 並在咱們繼續執行的時候, 異步讀取二進制信息. 若是未來任何調用涉及到GPU程序, 那會一直等到異步加載完成. 然而, 由於這是一個普通的模式, 見檢查了程序的連接狀態後, 當即連接(因此, 程序是在異步執行結束後當即運行的), 讓其忽略了這個選項.ide

磁盤緩存的思路關鍵

由於會一直存在磁盤裏面, 咱們須要包含任何會影響未被轉換的着色器的二進制內容. 這包含了對驅動器和可能在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, 再也不連接和再次緩存)
  • 若是正在加載緩存程序, 進行編譯和連接的話, 返回錯誤
相關文章
相關標籤/搜索