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)
下面的直方图会帮助咱们调整缓存
缓存大小分布计算(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, 再也不连接和再次缓存)
- 若是正在加载缓存程序, 进行编译和连接的话, 返回错误