【程序】Unity 性能 Review
发表于2018-01-21
本文首发于知乎专栏:MACK的游戏开发笔记,欢迎各位关注。
年初的一次优化笔记,第一篇。每个版本一周,每个版本一篇已经6,7篇了。过早的优化可能并不是最好的,但是每个版本每个月都要上线测试。始终觉得对于实时多人竞技游戏来说,流畅的重要性超过画面。
使用Unity的Profile先简单分析
- 角色更新过高,后面更详细的分析。
- 屏幕边缘图标和Wifi过高。优化方案:频繁设置UI文字和贴了可以优化。
- 放技能开销非常高,每个子弹有独立弹道逻辑,技能释放频率比价高。优化方案:类对象使用内存池,delegate优化减少gc,更完善的预加载机制,逻辑优化等等。CMT的timer使用内存池。
- 部分非技能的CMT没有预加载。
- 召唤物没有预加载。
- 部分道具有些没有预加载。
- 消息处理非常卡。解决方案:使用类对象内存池,优化序列化方式不使用protobuff的。
- 帧操作增减人会卡。解决方案:推测玩家行为预加载。
- 飞行道具开销很高,特别是Deactive上。解决方案:可见性使用layer,使用内存池
- 地图脚本开销较高。
- Camera.main直接调用开销较高。解决方案:因为Unity会遍历所有的Camera,是一个线性时间。在Update中调用的话,缓存一下main
camera。
使用Unity的FrameDebugger简单分析
- 俯视角开启了天空球渲染,其实没必要
使用Unity MemoryProfiler分析内存占用和内存泄漏
通过memoryprofile抓帧分析,内存占用不高,但是RenderTexture占用非常高63.5M
- 大厅场景RenderTexture 63.5m。解决方案:因为使用了景深,扰动,屏幕校色,实时阴影,等后期处理效果所致,我们根据玩家手机配置做了自适应和限定,并且再图形配置选项允许玩家做一些调整。景深开销非常高因此关闭了实时景深效果使用其他方法代替。
- 战斗场景Texture 33.7m Mesh 14.9m animationclip 3.7m Audiomanager 4.1M。解决方案:贴图压缩,使用shader减少换色贴图,修正一些贴图引用不释放导致的内存泄漏。
- LockstepMnanager.FixedUpdate函数 每帧分配10kb以上内存。解决方案:各种GC相关的优化。
- SceneLoader.update函数(315 bytes) MainLoop.Update函数(202bytes), NetManager.update函数(186bs) CharacterActor.LateUpdate函数(392 bytes)皆为每帧都分配。GC的分配每帧超过200b就算大了。解决方案:各种内存优化,消息使用内存池等等。
- MainLoop.OnGUI函数每帧分配内存 300b。解决方案:删掉空函数。
- 大厅场景Camera.Render cpu等待gnu渲染,造成时间帧率抖动 25ms-> 43ms 体现为Camera.Render self时间大幅变动 (优化方法 降低场景面数,拆分Gm_Cabin模型)
- Loading贴图存在伪内存泄漏,因为图集和按钮放在一起了被按钮引用。
- Unity技术支持提供了脚本插件可以分析代码中所有静态的引用。发现是lua的LuaScriptMgr中会对一些贴图有引用,需要注意使用Lua导致的内存问题。
使用XCode做详尽的性能优化
XCode也可以做GPU的性能分析。编译xcode工程需要Development版,启动,GUP的优化点需要在Run的面板里设置成metal否则有些堆栈看不到。点FPS,然后点相机Capture一帧,可以看到帧率,CPUGPU的占比,渲染时每个函数的开销。点Shader里面的看更详细堆栈,可以看到每个shader的每行代码的开销占比。
- 这个版本大厅很简单但是非常非常卡,看到UI和景深开销占比比较高,UI占了25,景深占了30。看到UI费是采样一张贴图占了50但是这张贴图并不大,景深费是因为shader里lerp了很多次导致开销很高。在FrameDebugger里可以看到UI最后渲染了一个全屏的面,可以看到每次渲染了占用的时间比,这个面实际没用。关闭了这个UI的全屏面,关了景深和后处理,基本就没开销了,连UI的渲染也只有2ms了因为景深把总线带宽给占满了,其中最费的还是景深效果。

- 版本机出的版本在XCode中会报错APPLE MACH-O Linker Error,无法做性能分析。解决方法:改成il2cpp了。
- 修复字符串拼接的量级,使用StringBuilder等。
- OnGUI,FixedUpdate,Update等空函数也会有gc开销,因为会产生从C++到C#层调用的开销。最好都去掉。
- FMOD音效模块开销过高。我们没使用官方的FMOD使用了FMODStudio,测试版资源为了效果和开发方便使用了一些事件内的效果器。优化后音效占用10-15%。
- 动画开销较高,使用了动作融合,动画状态机较复杂。解决方案:不可见的角色不更新动画,一些顶点受骨骼影响数可以设置很小,小怪等简单角色使用Animation,动画的一些优化等。
- UI,粒子,动作开销较高,并且多线程开销也高。
- 可以看到每个模块开销比较平均,已经做了很多优化了没有明显瓶颈,只能一个模块一个模块的抠了
其他一些测试以及和Unity官方支持的问答:
- Unity的物理比射线省,因为会优化做空间划分,只计算较小范围。Navmesh最优化。
- 迷雾和可以性检测可见性预缓存,预缓存分块加载,迷雾进行分块划分优化,分担计算量到多帧执行等等
- Resource目录资源多会导致Unity进游戏时间特别长,因为会把Resource下所有资源做一次检索,导致启动慢。建议使用Assertbundle,也支持压缩。另外提升进入游戏速度的就是,第一个场景足够简单足够小。解决方案:开发了热更的资源管理,每个资源有一个唯一ID,可以存在Resource下也可以存在Assertbundle中。
- Assetbundle的新算法是按Chunk加载,不会把整个Assertbundle都加载进来但是压缩比小,压缩的时候要选择ChunkBase。如果bundle加载好之后资源的引用时放在bundle上,如果a界面打开bundle包读取贴图,a界面关闭则贴图会释放,如果a没关闭,b界面读取贴图b界面会对应到同一张贴图上。
- 建议bundle放在StreamingAssets目录下。
- Profile分析建议:Overhead,是Unity没统计到的时间,用总计时间减去剩余的时间,一般包括C++到C#层的调用开销,场景复杂度,垂直同步等。大厅因为Overhead卡是因为CPU在等GPU,建议用XCode看。
- 通过Profile.BeginSample自己插代码对怀疑的代码进行内存和性能调试。
- Unity5.3.5p8修改了Foreach装箱拆箱导致的内存开销。但是没有修改mono的gc。实际测试可以用Foreach了。
- 部分Android设备总线太烂导致GC会特别卡
- OnGUI,FixedUpdate,Update等空函数也会有gc开销,因为会产生从C++到C#层调用的开销。最好都去掉
- 部分3G的Android设备,实际可用内存可能只有500M。Unity启动时最高能占到80M,一般在40-50M
- unload切场景自动调,gc也会自动调
- 编辑器下看到的资源引用计数不准确,得看真机。实际测试的时候老发现很多资源释放不掉,但是到真机测试发现实际已经释放掉了,因为编辑器模式会缓存一些资源,所有的测试都应该以真机为准。
- lua导致gc太多,没有比较好的解决办法,把lua的代码放到C# 层
- IOS上如果限45帧对IOS来说就是限30帧,低于30帧才是真实帧率
- CPU优化:ulua的计时器开销很大。其中PreloadManager是Unity的各个模块的预加载。
- GPU优化:Mask会很费。