空间换取时间
Unity内存优化
gc的优化
在游戏运行时数据主要存储在内存中
当游戏数据不需要时 存储当前数据的内存 就可以被回收再次使用
废弃数据所占据的内存 叫做 内存垃圾
GC(垃圾回收机制)是将废弃的内存重新回收并再次使用的过程
GC 是内存管理的一部分 如果废弃的内存过多 则会影响游戏的性能
Unity的内存管理机制
内存管理池:堆内存和堆栈的内存
堆栈stack主要用来存储较小的短暂数据
堆内存heap主要用来存较大的存储时间较长的数据
只要变量处于激活状态(有引用)则其占用的内存被标记为使用状态
一旦变量不再激活 则所占用的内存不在被需要 该内存可以被回收到内存池中再次使用 需要进行内存回收
GC垃圾回事 主要是指对堆上的内存分配和回收untiy会定时对堆内存进行GC操作
回收时
栈可以快速的移除或回收 是实时的
堆的内存不是及时回收的 如果一个变量不在被需要 此内存被标记为可回收
堆的分配
- 检测是否有足够的空间单元用来存储数据 如果有空间 则分配
- 如果没有足够的空间单元Unity会触发GC释放内存 如果释放后有足够大小的内存单元 则分配
- 如果释放内存后依然没有足够的空间单元Unity扩展内存大小然后分配
不管是触发GC 还是扩展堆内存 都是很缓慢的操作 所有尽量减少
GC每次执行的过程
- GC 检查内存中每个存储变量
- 检测变量的引用是否处于激活状态
- 如果变量的引用不是激活状态 添加可回收的标记
- 被标记为可回收的变量被移除 其所占内存会被回收到堆内存中
GC的问题
当需要分配新变量时 一旦内存单元不够GC就会频繁的触发
GC的触发 执行流程会消耗大量的时间 尤其是当堆内存中数据量比较大时
导致游戏运行缓慢 帧率运行低的问题
另一方面就是内存的碎片化 内存的分配是取决于变量的大小
当内存被回收后可能会使得内存被分割 形成多个碎片化的单元
即使调用了GC 堆内存总体内存变大了 但是独立的内存单元较小
下次内存分配依然找不到合适的内存单元 又会触发GC 且导致游戏内存越来越大
优化GC方案
- 减少GC的运行次数
- 减少单次GC的运行时间
- 空间GC的运行时间比如场景加载的时候调用
解决方案
- 减少new的次数
- 字符串拼接使用stringbuilder
- list创建时 规定内存大小
- foreach迭代器容易导致GC
- 使用枚举替代字符串变量
- gameobject.tag == “stf” 产生内存垃圾 GameObject.CompareTag(“stf”) 替代
- 不要在频繁调用的函数中反复调用进行堆内存分配比如 Update LateUpdate OnTriggerEnter
字符串优化
string是不可变的在C#中 每次对一个string对象进行修改 实际都是创建了一个新字符串
这样会频繁的对内存进行操作 分配内存导致碎片变多 也会导致多次触发GC
- stringbuilder在性能上和GC上都有很大的提升 会在长度不够的时候才重新申请内存和赋值
- 减少不必要的字符串的创建 缓存字符串
- 减少字符串的操作 比如制作UI时 Text中一部分经常改变 一部分不变 比如 “攻击力:100” 拆开成两个控件制作
- Debug.log函数 即使输出为空就会创建一个空字符串对象
缓存机制 Pool
频繁的内存操作 会带来极大的消耗和内存碎片的产生
所以所有需要频繁调用情况 都应该使用pool
把需要删除的物体 设置为非激活 然后在需要时再次激活它 复用对象实例
减少Instantiate和Destroy调用
Unity的API一些优化
transform.name
gameObject.tag
都会带来预想不到的内存分配 返回是新实例化的字符串
协程 yield return null 和 yield return 0 返回int值会涉及拆装箱
yield return new WaitForSeconds(1f) 不要每次都new — 缓存
AssetBundle 资源包优化 下载到本地后会生成一个内存镜像 这部分数据也会导致内存增加
而且AB包一般在游戏刚开始运行时加载 很容易导致游戏内的初始化内存占用过高 — 在适当的位置Unload(false)
Resources.Load方法 在需要的时候读取 在资源使用完成或者不在被需要时UnloadAsset UnLoadUnusedAsset 卸载
定时执行
在不影响游戏体验的情况下 主动调用GC操作 System.GC.Collect()