性能优化(36)——Shader与托管内存优化


 

Shdaer的内存问题

  • 引起Shader内存的主要原因是Shader的关键字过多,导致Shader的变体过多,打包时如果变体剔除做得不好的话,就会导致Shader的内存过高。因此做好Shader变体的剔除是控制Shader内存占用量的关键

变体剔除与变体收集

  • 是为了让运行时Shader的内存占用更少,剔除掉不会被使用的Shader变体

  • 变体收集是为了做Shader的WarmUp,目的是为了避免运行时Shader加载编译导致的性能问题

  • 变体收集可以用来辅助变体剔除工作,可以作为不被剔除的Shader的参考

  • 对于Shader变体非常多的情况下,做到变体完全剔除干净或变体完全收集完整是个很难做到的工作。这种情况下,我们只需要保证变体剔除尽量干净、收集尽量完整即可。中间一部分冗余占用的内存在可接受范围内时是可以被忽略的

查看具体Shader会产生多少个Shader变体

  • 一般我们是通过Shader的Inspector面板来查看的

  • 但由于平台不同,不同的平台或图形API需要切换到对应平台或API上才能统计正确。而且每个Shader都要做统计,一些Unity内置的Shader也难以统计。这样就有些麻烦了

  • 这时我们可以通过打包后的Editor.log来查看Shader变体个数与Shader变体剔除的统计结果

  • 在文件中搜索Compiling shader字段,这时会列出每个Shader的变体个数和实际打包到包里的变体个数

  • 具体Shader在实际平台占用的内存还需要在移动设备上运行时通过Memory Profiler进行抓取查看

托管内存优化

  • 具体会与用户脚本、开发者编码习惯直接相关

  • C#语言层面:

    • Boxing Allocation装箱操作:值类型转换到引用类型的转换,会造成额外的内存开销。使用Lua等脚本语言做热更新逻辑的开发者,在封装交互接口时,尽量不要传递Unity引擎里定义的对象,尽量保证调用的函数接口传递或返回基础类型值对象
    • String字符串拼接:做字符串拼接应尽量使用StringBuilder进行
    • 闭包分配:一般是指代码段中使用了代码段之外定义的变量,如匿名函数或Lambda表达式,当调用这一类代码段时,托管堆中会生成一个类来保存这些代码段中引用到的外部变量,会有额外的内存开销
    • 避免使用Linq库写任何游戏内的代码:它们会生成大量托管堆上的垃圾内存
  • Unity相关代码调用

    • Unity中提供了很多NonAlloc函数(无托管内存开销的函数),与之对应的是有托管内存开销的函数
      • 如:Physics.RayCastAll对应的无托管内存开销的函数是Physics.RayCastAllNonAlloc
    • Unity下一些返回对象列表的函数都是有托管内存开销的。如:
      • Unity.Object.FindObjectsOfType
      • UnityEngine.Component.GetComponentsInParent
      • UnityEngine.Component.GetComponentsInChild等
    • 一些Unity类成员变量访问的方式:如直接通过”.”调用的成员变量,应尽量采用”.Get”的方式去调用。如:
      • UnityEngine.Mesh.vertices => UnityEngine.Mesh.GetVertices,
      • UnityEngine.Mesh.uv => UnityEngine.Mesh.GetUV,
      • UnityEngine.Renderer.sharedMaterials => UnityEngine.Renderer.GetSharedMaterials,
      • Unity.Input.touches => Unity.Input.GetTouches等
  • 无论是C#还是Unity相关的代码调用,如果发生在循环或者每帧更新的函数里,造成的托管内存开销会更大

  • 我们尽量还需要注意,不要在循环或每帧更新的逻辑中反复去Instance对象,应尽量在初始化时利用内存池预先分配好,避免在运行过程中的GC开销

  • 当你发现你的游戏每帧GC开销变化过大时,可以开启BuildSetting-Player中的增量GC选项,它可以将一帧中的GCCollect调用分摊到多帧进行,可以有效地平滑你的游戏帧率

  • BuildSetting中的关于程序集优化的配置:在Optimization标签下,默认会勾选Strip Engine Code选项,开启这个选项后,如果backend是IL2CPP的情况下,Unity会删除项目不使用的Unity引擎的代码,强烈建议开启,不仅会对包体大小有优化,对应用程序内存的占用也可以减小

  • 这个选项下面的Managed Stripping Level,可以根据包体和内存的需求进行配置,它会根据不同的选项自动删除托管dll中的不使用的代码,同样也可以减小包体的同时减少内存的占用,不过当设置的级别过高时会导致误删某些C#接口导致编译出错,这时你可以为误删除的接口打上Preserve的属性标签以防止这个接口被删除。也可以使用linker.xml文件进行更详细的配置,不过这个选项只针对需要极限优化的项目,如果嫌配置麻烦,默认选项就好