Key points from a Unite talk about performance optimizations for mobile games in Unity.
Video 1. Optimizing Mobile Applications. June 2016, Unite Europe
- iOS: Instruments
- Android: VTune, Snapdragon Profiler
- UnityEditor: Profiler, Timeline, Memory Profiler.
- free iOS profiler included with XCode
- works with IL2CPP, profiles Unity native code too
- best tool for CPU profiling and startup time profiling
- how to start: blogs.unity3d.com/...
- (some examples of where a typical workload may be situated in a callstack).
5.3+ Memory Profiler:
- free, available at: bitbucket.org/Unit...
- only works in IL2CPP builds
- allows to get memory snapshot and track down the objects which are referencing resources
- helps to identify problem when the same resources from AssetBundles are loaded into memory multiple times and have same content but different instance ids.
Tips for memory usage:
RenderTextures for effects in size on mobile devices (iPad with retina uses 4k full-screen texture, consider half-res)
- wrap resource management into your own system to track which asset bundles are already loaded
- make your asset importer to ensure all assets are properly (auto) compressed and organized
- there is a mip-map mode in game view in the editor which shows whether the texture is too high for an object or too low
- Read/Write enabling on textures will double the memory size the texture takes at runtime
- Read/Write enabling ("on" by default) on meshes will also double the memory size (but "on" is required for mesh collider generation)
- disable Rig on non-animated models to prevent from
Animator being instantiated at runtime
- enable mesh compression
- if you instantiate meshes in runtime - keep in mind the default
MeshRenderer will be instantiated with "Cast/receive shadows - on", "Reflection probes - on", "Motion vectors - on". And if you are not using shadows, then this single
MeshRenderer may enable the shadow pass before regular rendering
- audio is compressed at build time; use MP3 for iOS (supported and optimized) and Vorbis for Android; consider "Force Modo" for mobiles
- Mono heap never shrinks - only expand, so don't forget to trigger GC.Collect() after long operations with non-responsive UI (loading screens, scene switches)
- reuse your collections to avoid allocations, avoid boxing and strings
- closures and anonymous methods are sources of memory allocations (they are not displayed in profiler yet)
HashSets) use general implementation of
Comparer, so if keys are value type - each operation will cause boxing of a value type (to pass it to the
Comparer) and instantiation of the
Comparer itself; the solution is to implement your own comparer which works with your value types w/o converting them to objects and pass it to Dictionary's constructor
- don't use
- some Unity API returns arrays - just don't call those methods too often and sometime there is a way to avoid allocations at all by using other API methods
- be aware of
params arguments because they are allocating array with each call.
Tips for Start-up time:
- forget about json or xml representation of your data in production code: parsers are too slow (and some of them are even based on
Reflection); if you need - use builtin
JsonUtility class which is based on internal serialization system and pretty fast
- use binary serialization
- split parsing into multiple frames or use threads for deserialization
- cache previous deserialization results
- parse only what you need (parts of files or split data into many small files)
- the more files in your "Resources" folder - the longer will Unity load it's index on app startup (for thousands of files it's already a noticeable time); consider moving majority of them to
AssetBundles and load them on demand.
Tips for runtime:
- use Animator and Shader property ids (it's safe to call
Animator.StringToHash() in static constructors or initializers of static classes)
- Unity doesn't have a generational garbage collector like in Mono or .Net, all the reference-type objects are stored in a simple heap, so if there are a lot of boxed objects - the GC have to scan through all of them each
Collect() (a lot of CPU time)
- don't use regular expressions - they generate an enormous garbage and very slow (it's not a Java runtime here!)
StringComparison.Ordinal for all string comparisons to compare string bytes values - otherwise you can create locale-dependent bugs because of string coercion (which is also slow)
String.EndsWith() are 10-100x slower than a hand-coded byte-wise comparisons
- any method call (even property getter or setter) is an additional CPU time waste (also that's why
List<> accessor is slower than array accessor)
- virtual or interface methods will never be inlined (even in IL2CPP), the same stands for property getters/setters (that's why
Vector.zero is slower than