68.GPU影响因素之缓存和显存

68.性能优化-GPU-影响因素-缓存和显存


68.1 知识点

缓存和显存的基本概念

显存

在之前的内存带宽相关知识中,已经了解过显存了。它是 GPU 外部的专用大容量存储,主要用于存放:

  1. 顶点
  2. 纹理
  3. 渲染目标(RT)
  4. 阴影贴图
  5. 后处理缓冲

特点:

  1. 容量大,以 GB 计,带宽高,但延迟大(相比缓存慢得多)。
  2. GPU 工作时几乎会不停地从显存中读写内容。

缓存

缓存是 GPU 内部的小容量高速存储,主要用于存放从显存取出的热点数据,方便重复利用。

所谓热点数据(Hot Data),在 CPU 和 GPU 的优化领域中指的是在短时间内被高频访问的数据。因为它被用的多,所以放在更快的存储空间中(比如寄存器、缓存)能极大提升性能。

GPU 中常见的热点数据:

  1. 顶点缓存
    渲染一个网格时,某些顶点会被多个三角形复用。GPU 会把最近变换过的顶点存在缓存里,避免重复计算。
  2. 纹理缓存
    当片元连续访问相邻的 UV 时,缓存会把周边像素块一起取进来。
  3. 常量缓存
    灯光参数、矩阵等,几乎每个片元都要用。

特点:

  1. 容量小,以 KB~MB 计,速度快。
  2. 命中(缓存中找得到对应数据)时快,不命中(找不到)时必须回显存。

补充:缓存的工作原理

1. 缓存块
缓存不会一字节一字节存,而是一次性把一大块连续的数据搬进来。
原因:利用空间局部性,取了一个地址,附近的数据很可能也会用。

2. 索引映射
缓存的容量有限,需要一个规则来决定某个内存地址的数据放在哪个缓存槽里。常见规则:

  • 直接映射(Direct Mapped):一个内存块只能放在某个固定的缓存槽。
  • 组相联(Set Associative):一个内存块可以放到一组缓存槽里,组内用替换策略选位置。
  • 全相联(Fully Associative):一个内存块可以放到任何缓存槽(灵活但查找慢)。
  • 高组相联(High Associativity):当一个内存地址映射到某一组时,它有很多个槽位可以选择存放,几乎接近全相联。

GPU 中一般使用高组相联缓存。

3. 替换策略
当缓存满了,常见的替换规则:

  • LRU(Least Recently Used,最近最少使用)
  • FIFO(先进先出)
  • Random(随机,GPU 上常见,因为简单 + 并行)

显存和缓存的关系

GPU 和 CPU 中都存在这个概念,原理是一致的。

1. 取数据时
当 GPU 或 CPU 访问某个地址:

  • 缓存命中(在缓存中找到了):直接从缓存中读取,速度快。
  • 缓存未命中(在缓存中没找到):去显存中取,同时把这块数据搬进缓存。

2. 往缓存里放数据时
缓存不是一字节一字节放,而是一次性搬一整块(通常 32B / 64B / 128B)。
这样就能利用空间局部性,要用的数据附近的数据也很可能会用,就顺便搬进来。

3. 替换缓存数据时

  • 如果缓存还有空:直接放进去。
  • 如果缓存满了:根据数据替换规则直接覆盖之前的数据(GPU 一般采用 Random 随机替换、CPU 一般采用 LRU 最近最少使用的替换)。

通俗理解

  • 显存就像仓库。
  • 缓存像桌上的小抽屉。
  • 去仓库取货很慢,从面前的抽屉里取货快得多。

为什么缓存和显存会影响性能

显存

性能瓶颈主要来自于:

  1. 内存带宽不足(之前详细讲过)。
  2. 容量不足

显存决定能装多少、能传多快,当超出上限就存在性能问题。

缓存

性能瓶颈主要来自于:

  1. 未命中
    比如在 Shader 中进行随机采样纹理、UV 跨度过大,就会导致缓存命中率低。
    当未命中就会频繁地去显存中取数据,导致获取数据缓慢,延迟变高。
  2. OverDraw、重复访问
    一个像素被多次覆盖,导致反复读取纹理等信息,不停替换缓存中内容,缓存中内容还没有重复使用就被替换了。
    让缓存命中率下降,缓存的加速作用大幅削弱。

缓存决定能不能就近取、取得快不快,当取不到就存在性能问题。

缓存和显存的优化思路

显存优化

主要的优化思路就是容量和带宽的优化。由于内存带宽之前讲解过优化思路,这里不赘述,主要讲解容量优化思路。

1. 资源压缩

  • 纹理压缩
    在 Unity 中选择更节约空间的压缩格式,比如 ASTC、ETC2 等。
  • 法线压缩
    双通道压缩,只存法线的 X 和 Y 分量,Z 分量在运行时计算。可以在 Unity 中选择 ETC2、EAC、RG 等压缩格式。

2. 降低资源规格

  • 降低贴图分辨率
  • 减少 G-Buffer 和 Render Target 数量
    G-Buffer 是延迟渲染中的缓冲区,Render Target 是各种缓冲区(颜色、深度、法线等,包括 RenderTexture)。
    可以通过合并属性(比如合并金属度、粗糙度等属性)、降低 RT 的分辨率、屏幕后处理效果使用半分辨率、移动平台使用前向渲染路径避免延迟渲染等方式减少。

3. Mipmap + Streaming

使用 Mipmap,远处只保留低分辨率层级。在 Texture Importer 面板勾选 Streaming Mipmaps,按需加载纹理(Texture Streaming)。Unity 会根据相机和屏幕大小,自动决定加载哪些 Mipmap 层,可以减少显存占用和显存带宽消耗。

4. 静态资源烘焙

  • 光照烘焙:将静态光照放入光照烘焙贴图里,避免实时阴影、光照贴图常驻内存。
  • 反射探针烘焙:静态场景用烘焙 Cubemap,避免大量实时探针。

缓存优化

1. 提高局部性

  • 纹理访问局部性
    UV 连续,避免大跨度跳跃;合并图集。
  • 顶点缓存命中
    三角形索引优化:相邻三角形尽量复用顶点。
    避免退化三角形:面积为零的三角形,也就是三个顶点中有两个或三个是重合的情况。

2. 减少缓存未命中

  • 降低 OverDraw
  • 避免依赖采样
    依赖采样是指片元着色器里,纹理采样的坐标不是直接来自插值,而是运行时计算出来的。
    GPU 的纹理单元本来可以提前准备需要取的区域,但依赖采样让它必须等前一步结果。
    普通采样 UV 连续,相邻像素会访问相邻内容,缓存命中率高。
    依赖采样 UV 可能随机跳,缓存命中率低,频繁回显存。
    应尽量减少采样坐标依赖前一次计算的情况,可以采用预计算预烘焙到纹理中直接获取相关信息,尽量简化 UV 的计算。

3. 减少数据占用

  • 数据轻量化
    顶点属性尽量用低精度的,比如用 half 替代 float
    顶点着色器的输出结构体 v2f 中,只传必要数据。
  • 共享内存
    在 Shader 中,用共享内存替代反复访问。

68.2 知识点代码

Lesson68_性能优化_GPU_影响因素_缓存与显存.cs

public class Lesson68_性能优化_GPU_影响因素_缓存与显存
{
    #region 知识点一 缓存和显存的基本概念

    #region 显存

    //在之前的内存带宽相关知识中,我们已经了解过显存了
    //它是GPU 外部的专用大容量存储
    //主要用于存放
    //1.顶点
    //2.纹理
    //3.渲染目标(RT)
    //4.阴影贴图
    //5.后处理缓冲
    //等等

    //特点:
    //1.容量大,以GB计,带宽高,但是延迟大(相比缓存慢得多)
    //2.GPU工作时几乎会不停地从显存中读写内容

    #endregion

    #region 缓存

    //它是GPU 内部的小容量高速存储
    //主要用于存放
    //从显存取出的热点数据,方便我们重复利用

    //所谓热点数据(Hot Data)
    //一般在CPU和GPU的优化领域中指的是
    //在短时间内被高频访问的数据
    //因为它被用的多,所以放在更快的存储空间中(比如寄存器、缓存)能极大的提升性能
    //比如在GPU中的常见热点数据为
    //1.顶点缓存
    //  渲染一个网格时,某些顶点会被多个三角形复用
    //  GPU 会把最近变换过的顶点存在 缓存 里,避免重复计算
    //2.纹理缓存
    //  当片元连续访问相邻的 UV 时,缓存会把周边像素块一起取进来
    //3.常量缓存
    //  灯光参数、矩阵等,几乎每个片元都要用

    //特点:
    //1.容量小,以KB~MB计,速度快
    //2.命中(缓存中找得到对应数据)时快,不命中(找不到)时必须回显存

    //补充——缓存的工作原理
    //1.缓存块
    //  缓存不会一字节一字节存,而是一次性把一大块连续的数据搬进来
    //  原因:利用 空间局部性,取了一个地址,附近的数据很可能也会用
    //2.索引映射
    //  缓存的容量有限,需要一个规则来决定某个内存地址的数据放在哪个缓存槽里
    //  常见规则:
    //  2-1.直接映射(Direct Mapped):一个内存块只能放在某个固定的缓存槽
    //  2-2.组相联(Set Associative):一个内存块可以放到一组缓存槽里,组内用替换策略选位置
    //  2-3.全相联(Fully Associative):一个内存块可以放到任何缓存槽(灵活但查找慢)
    //  2-4.高组相联(High Associativity):当一个内存地址映射到某一组时,它有很多个槽位可以选择存放,几乎接近全相联
    //  GPU中一般使用 高组相联缓存
    //3.替换策略
    //  当缓存满了,替换策略常见规则
    //  3-1.LRU(Least Recently Used,最近最少使用)
    //  3-2.FIFO(先进先出)
    //  3-3.Random(随机,GPU 上常见,因为简单+并行)

    #endregion

    #region 显存和缓存的关系

    //注意:GPU和CPU中都存在这个概念,原理是一致的
    //1.取数据时
    //  当GPU或CPU访问某个地址
    //  缓存中命中(在缓存中找到了):直接从缓存中读取,速度快
    //  缓存未命中(在缓存中没找到):去显存中取,同时把这块数据搬进缓存
    //2.往缓存里放数据时
    //  缓存不是一字节一字节放,而是一次性搬一整块(通常 32B/64B/128B)
    //  这样就能利用空间局部性,如果你要用的数据附近的数据也很可能会用,就顺便搬进来
    //3.替换缓存数据时
    //  如果缓存还有空:直接放进去
    //  如果缓存满了:根据数据替换规则直接覆盖之前的数据(GPU一般采用Random随机替换、CPU一般采用LRU最近最少使用的替换)

    #endregion

    #region 说人话

    //显存就像仓库
    //缓存像桌上的小抽屉
    //去仓库取货很慢
    //从面前的抽屉里取货快得多

    #endregion

    #endregion

    #region 知识点二 为什么缓存和显存会影响性能

    #region 显存

    //性能瓶颈主要来自于:
    //1.内存带宽不足(之前详细讲过)
    //2.容量不足

    //说人话
    //显存决定能装多少,能传多快
    //当超出上限,就存在性能问题

    #endregion

    #region 缓存

    //性能瓶颈主要来自于:
    //1.未命中
    //  比如
    //  在Shader中进行随机采样纹理、UV跨度过大
    //  就会导致缓存命中率低
    //  当未命中就会频繁地去显存中取数据,导致获取数据缓慢,延迟变高
    //2.OverDraw、重复访问
    //  一个像素被多次覆盖,导致反复的读取纹理等信息,不停替换缓存中内容,缓存中内容还没有重复使用就被替换了
    //  让缓存命中率下降,缓存的加速作用大幅削弱

    //说人话
    //缓存决定能不能就近取,取的快不快
    //当取不到,就存在性能问题

    #endregion

    #endregion

    #region 知识点三 缓存和显存的优化思路

    #region 显存

    //主要的优化思路就是 容量 和 带宽 的优化
    //由于内存带宽之前讲解过优化思路,这里不赘述
    //这里主要讲解 容量 优化思路
    //1.资源压缩
    //  1-1.纹理压缩
    //      Unity选择更节约空间的压缩格式,比如ASTC、ETC2等
    //  1-2.法线压缩等
    //      双通道压缩,只存法线的X和Y分量,Z分量在运行时计算
    //      可以在Unity中选择ETC2、EAC、RG等压缩格式
    //  等等

    //2.降低资源规格
    //  2-1.降低贴图分辨率
    //  2-2.减少G-Buffer(延迟渲染中缓冲区)和Render Target(各种缓冲区、颜色、深度、法线等等,包括RenderTexture)数量
    //      可以通过合并属性,比如合并金属度、粗糙度等属性
    //      可以降低RT的分辨率,屏幕后处理效果使用半分辨率
    //      移动平台 使用前向渲染路径,避免延迟渲染 等等
    //  等等

    //3.Mipmap+Streaming
    //  使用 Mipmap,远处只保留低分辨率层级
    //  在 Texture Importer 面板勾选 Streaming Mipmaps
    //  按需加载纹理(Texture Streaming)
    //  Unity 会根据相机和屏幕大小,自动决定加载哪些 Mipmap 层
    //  可以减少显存占用和显存带宽消耗

    //4.静态资源烘焙
    //  光照烘焙:将静态光照放入光照烘焙贴图里,避免实时阴影、光照贴图常驻内存
    //  反射探针烘焙:静态场景用烘焙 Cubemap,避免大量实时探针

    //等等

    #endregion

    #region 缓存

    //1.提高局部性
    //  1-1.纹理访问局部性
    //      UV 连续,避免大跨度跳跃
    //      合并图集
    //  1-2.顶点缓存命中
    //      三角形索引优化:相邻三角形尽量复用顶点
    //      避免退化三角形:面积为零的三角形,也就是三个顶点中有两个或三个是重合的情况
    //2.减少缓存未命中
    //  2-1.降低OverDraw
    //  2-2.避免依赖采样(片元着色器里,纹理采样的坐标不是直接来自插值,而是运行时计算出来的)
    //      GPU 的纹理单元本来可以提前准备需要取的区域,但依赖采样让它必须等前一步结果
    //      普通采样UV 连续,相邻像素会访问相邻内容,缓存命中率高
    //      依赖采样UV 可能随机跳,缓存命中率低,频繁回显存
    //      我们应该尽量减少采样坐标依赖前一次计算的情况,可以采用预计算预烘焙到纹理中直接获取相关信息
    //      要尽量简化UV的计算
    //3.减少数据占用
    //  3-1.数据轻量化
    //      顶点属性尽量用低精度的,比如用half替代float
    //      顶点着色器的输出结构体中v2f,只传必要数据
    //  3-2.共享内存
    //      在Shader中,用共享内存替代反复访问

    //等等

    #endregion

    #endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏