概念理解
4种内存类型
1: Unity底层(C++层,本机堆,核心代码,unityengine.dll等一系列别称)占用的内存,包含Scene,Audio,CodeSize,贴图等.Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。基本理念是,如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。听起来很美好也和托管堆一样,但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),也是Unity给人留下“吃内存”印象的罪魁祸首。
2: Unity的托管堆(Managed Heap),托管内存,也被称为用户管理的内存,Mono项目一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用,从而导致Mono认为这块内存一直有用,而无法回收。profile可以检测到.并且需要手机查找才正常,经常需要优化的部分.
3: 代码文件的内存.如lua脚本本身的内存.C#泛型类的内存,C++层会将泛型编译成静态类型就多出很多文件,尽量注意不要使用太多泛型模板类,shader写的一个,但是编译成很多份的文件等,都是代码文件会占用的内存.
程序代码包括了所有的Unity引擎,使用的库,以及你所写的所有的游戏代码。在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库。
4: 第三方库的占用的内存,也被称为用户管理的内存,但是已经和unity的内存关系上完全无关了,如自己使用C++编译的库,tolua占用的内存,lua文件占用的内存(这个不是unity管理的),lua占用的内存. 并且unity编辑器的profile无法检测到.
优化重点以及方向
unity托管内存>第三方库(主要是lua优化)>unity底层优化>代码文件.
unity托管内存(用户管理内存)-->
VM : mono的一个VM内存池,虚拟机的内存池.VM内存会返回内存给OS内存,当一块内存 GC 6次没有被访问到,就会将内存放回给OS
GC :
分代式内存回收
GC机制考量,
Throughput(回收能力):一次GC会回收到多少内存
pause times(暂停时长):一次GC对主线程影响多大,会让主线程暂停多少毫秒
Fragmentation(碎片化):一次GC内存回收之后,会让整块内存的碎片化增加多少,即不连续内存会增加多少.
Mutator overhead(额外消耗):回收本身有消耗,需要考量这个消耗有多大.
Soalability(可扩展性):多核多线程时会不会有其他bug
Portability(可移植性):在其他平台上面是否可以移植
目前GC:
unity现在使用的是:Boehm(Non-generational)非分代式,(Non-compaction)非压缩方式,内存回收机制,是所有内存同一放在一起的.造成主线程的卡顿
下一代GC:
Incremental GC(渐进式GC),分帧去做GC回收,GC时长还是一样的,但是会避免系统卡顿
目前已转向IL2CPP(升级的Boehm)了.
问题:内存下降,但是总体的内存池还是上升了,为什么?
是代码碎片化导致的.因为有一块内存一直插不进去当前内存池里面,只能另行开辟内存.也就是内存碎片化没有被压缩.优化建议,先去对大型内存创建和释放,再对小型内存进行创建和释放.
Zimbie Memory(僵尸内存,别名就叫做脏内存,无用内存)
指内存从开启游戏到游戏关闭只用了一次内存.
没有被释放,内存也没有泄露,也没办法使用.
内存泄漏指得是没有任何人可以访问和管理到它,也没法释放掉.
优化建议:
不要觉得 obj == null就是释放掉内存了,显示调用destroy方法才可以释放掉.
要常用struct不要用class
Pool In Pool (池中池) 高频使用的小部件需要建立一个内存池,不要频繁的去创建销毁
Closures and anonymous methods (闭包和匿名函数,协程) 这些东西最终在C++层全部new成一个class了,优化建议:不要用.关于协程的优化建议:用的时候创建,及时销毁.再用时,再创建,再销毁.
配置表优化,优化建议,采用C++管理内存,使用C#接口以及Lua接口去查询,而不是在c#和Lua里面保存内存,不要在c#和Lua里面重新全部记录.例如:从C++接过配置表对象时,在c#里面再保存一份,这是错误的方式.
Singleton 单例慎用,仅在必要时用,最好不要用单例模板,太坑爹的设计了,难用不说还特么代码难看懂,恶心.
这个地方严重指责unity的GC机制,一直再说历史原因什么的,现在也不做内存压缩,程序用着这个挺恶心的.
第三方库(主要是lua优化)-->
需要查看tolua源码以及C++基础,然后在tolua的基础上面做优化,需要的个人能力比较强.
以及使用C++库作为插件导入进unity里面使用.
unity底层代码优化方向-->
底层会根据类型将内存分配到不同的Allooator的池子里面,使用GetRuntimeMemory
profiler中Memory一项中的Used Total 和 Reserved Total会保持相同的上升或者下降,如果不一致,那就是出bug了.
Used Total 表示当前你使用了多少内存
Reserved Total表示unity申请了多少内存,将要使用到的内存
1: Scene中的GameObject太多,则内存会暴涨.如果在场景中创建了一个GameObject,底层C++会创建一个或者多个object(记录component),去记录这个GameObject的信息.是程序的优化重点,建议:尽量少创建GameObject,并且无用GameObject尽量干掉,优化GameObject个数.
2: Audio中会有个缓存池(DSP buffer),需要用户设置,填充满就会向CPU发送播放指令,发送完播放指令才会播放.设置的过大,则音频填充满就会时间过长,声音延迟,设置的过小,就会向CPU频繁发送.优化建议:测试音频播放密集时与不密集时的样本,并进行大小变换设置.
Force to mono,双声道音频设置,双声道概念:左右声道播放的不一致的声音.优化建议,设置为单声道.
音频格式Format,Mac/iOS上面的音频设置为mp3,有硬件支持.
音频压缩格式Compression Format:在哪种情况下使用哪种形态,需要去unity官方手册上面查询.
3: CodeSize,模板泛型的乱用,会导致打包速度,并且静态代码量增多.模板泛型,(il2cpp)会在C++层将泛型全部转成静态类型的C++,会变的很大.(一个文件2-3m已经很多了吧,官方视频上说有25M! ...)
4: AssetBundle
typetree,序列化的时候会生成一个typetree,反序列化的时候会根据这个typetree反序列化.
关闭typetree内存会减小,包大小会减小,build和运行时会变快.
Lz4 压缩方式,快速压缩,包体大Lzma上面30%,不要用Lzma,lzma会增大内存等等副作用.
asset会有头记录部分,以及实际的数据部分,如果每一个都打成ab则有可能发生,头记录部分比实际数据部分还要大,建议是1-2M,5G以后可以加大.
5:Resource 文件夹 ,打进包的时候会做一个红黑树(R-B Tree),会检索数据在什么位置,这个里面的数据很大,则红黑树就会很大.优化建议:不要放大量数据就行了,用ab替换.
6:Texture
upload buffer 这个数据表示填满之后向GPU发送一次,建议测试并平均该值
R/W read and write 一般情况下不要开,开启情况会在显存和内存中各一份.
mip maps UI情况下不要开就行了.
7: mesh
R/W 一般情况下不要开. compression在某些unity版本上面开了比不开占用更多内存.
(tips:加快 unity编辑器下脚本编译速度https://www.xuanyusong.com/archives/4474)