68.性能优化-GPU-影响因素-缓存和显存
68.1 知识点
缓存和显存的基本概念
显存
在之前的内存带宽相关知识中,已经了解过显存了。它是 GPU 外部的专用大容量存储,主要用于存放:
- 顶点
- 纹理
- 渲染目标(RT)
- 阴影贴图
- 后处理缓冲
特点:
- 容量大,以 GB 计,带宽高,但延迟大(相比缓存慢得多)。
- GPU 工作时几乎会不停地从显存中读写内容。
缓存
缓存是 GPU 内部的小容量高速存储,主要用于存放从显存取出的热点数据,方便重复利用。
所谓热点数据(Hot Data),在 CPU 和 GPU 的优化领域中指的是在短时间内被高频访问的数据。因为它被用的多,所以放在更快的存储空间中(比如寄存器、缓存)能极大提升性能。
GPU 中常见的热点数据:
- 顶点缓存
渲染一个网格时,某些顶点会被多个三角形复用。GPU 会把最近变换过的顶点存在缓存里,避免重复计算。 - 纹理缓存
当片元连续访问相邻的 UV 时,缓存会把周边像素块一起取进来。 - 常量缓存
灯光参数、矩阵等,几乎每个片元都要用。
特点:
- 容量小,以 KB~MB 计,速度快。
- 命中(缓存中找得到对应数据)时快,不命中(找不到)时必须回显存。
补充:缓存的工作原理
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 最近最少使用的替换)。
通俗理解
- 显存就像仓库。
- 缓存像桌上的小抽屉。
- 去仓库取货很慢,从面前的抽屉里取货快得多。
为什么缓存和显存会影响性能
显存
性能瓶颈主要来自于:
- 内存带宽不足(之前详细讲过)。
- 容量不足。
显存决定能装多少、能传多快,当超出上限就存在性能问题。
缓存
性能瓶颈主要来自于:
- 未命中
比如在 Shader 中进行随机采样纹理、UV 跨度过大,就会导致缓存命中率低。
当未命中就会频繁地去显存中取数据,导致获取数据缓慢,延迟变高。 - 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