Best optimization tips by Unity engineers at Unite
Compilation of best Unity tips by Unity engineers gathered at Unite conference. I spent a bunch of time rewatching these talks so you don’t have to.
There is an amazing series of talks at Unite about optimization by the awesome speaker Ian Dundore.
I personally watched it a few times myself over the years. Once at work I needed to provide the source for the optimization tip I had given, but of course I didn’t remember off the top of my head in which talk I had seen it. I recalled that I had seen it in one of the talks listed in this blog post, but still had to spend some time looking for the exact tip.
So I decided to create a short list of takeaways from these videos, so I myself and everyone else can get back to it and refresh their knowledge without spending hours of time on x2 playback speed. All points have time codes, so you can go directly to where he talks about it and get more info about the performance issue and how to resolve it.
Obviously some of these videos are kind of old now and not all advice can be applied, because some performance issues Ian talks about are already fixed in Unity internally. But most of these tips are still relevant and not circulating over the internet constantly. So without further ado, let’s get to the list.
Unite 2016 – Let’s Talk (Content) Optimization
|04:45||Unity serializes everything you have in a scene and prefabs. There is no duplicate data checks and no compression.|
|07:20||All serialized data is included in builds.|
|13:10||Use nested prefabs for repetitive elements.|
|18:00||Move configuration data to ScriptableObjects from MonoBehaviors to avoid serialization of repetitive data inside each prefab or scene file (got the blog post recently mentioning this point and some others too).|
|19:35||Pass parent transform into |
|20:05||Since Unity 5.4 transforms are stored in a contagious buffer sorted depth first, so if you need to iterate all children of a root transform, then make sure to use the depth-first algorithm.|
|23:55||Do not use transforms as “folders” in your scenes.|
|24:00||Do not reparent objects when using Object Pool pattern unless absolutely necessary. At least in release builds.|
|26:10||Apply changes to transforms at once, adding them up if you have several changes in one frame to avoid an internal OnTransformChanged callback being fired many times. Should be changed in 5.6 to pull model to avoid this issue.|
|28:10||Use Optimize Game Object box on skinned meshes to greatly increase performance by allowing Unity to parallelize processing of bones and improve loading times since it could reduce the amount of transforms in a scene significantly if skinned meshes are used heavily.|
|33:15||Disable the parent Canvas component instead of GameObject. Text component is especially heavy with it’s OnEnable, since UI Text dirties itself in OnEnable. Do not forget to disable MonoBehaviour callbacks like Update() manually in that case.|
|37:30||Merge layered UI elements. All Unity UI is drawn as Transparent geometry, so every pixel in every quad will be sampled and no occlusion culling is done there.|
|39:00||Split canvases. Keep dynamic and static elements in separate canvases. Keep a balance between extra draw calls vs cost of batching.|
Unite Europe 2017 – Squeezing Unity: Tips for raising performance
|03:00||Profile your code first, before any optimization attempt. The thing I mention heavily in my blog post on one micro-optimization https://gamedev.center/array-vs-dictionary-lookup-in-net/|
|08:15||Reduce amount of API calls that return an array. Make sure to use non-allocating equivalent if available.|
|10:00||Know how your data structures work internally. When to use Array, List, Dictionary, HashSet etc.|
|13:30||You can use double collection when you need to iterate frequently over the collection, but also need key access.|
|15:50||If you need Dictionary with MonoBehaviour or ScriptableObject key, then use |
|17:45||The C# integer-keyed dictionary is highly optimized with hand-written IL code by the Xamarin team. But don’t use it every time, use only for dictionaries that are indexed 100-1000s times per frame.|
|22:00||Use inlining for very hot path methods. You can even consider manual inlining, but profile first. After .NET 4.6 inlining via attribute should be fine too.|
|23:00||Use fields instead of properties in a hot path.|
|25:00||What happens when Canvas rebuilds its mesh: recalculate layout, regenerate mesh, regenerate materials.|
|25:15||When UI element is dirty the mesh for it will be regenerated even if alpha is 0.|
|26:17||Changing the color of a graphic only dirties it’s vertices. Changing Fill* properties also dirties only vertices. So no Canvas mesh rebuild is needed.|
|26:40||UI elements are sorted by depth, yes, it includes sort() operation call, which is O(n log n) operation.|
Again the tip from the previous year: UI is transparent, so everything is rendered all the time – overdraw can be a problem.
|28:35||Nesting canvases is a good practice, as dirtying a child canvas does not dirty the parent. Child canvases maintain own geometry and perform own batching.|
|29:00||When splitting canvases, group elements that are updated simultaneously.|
Separate dynamic elements from static ones.
|30:15||Use object pooling for large ScrollRects with many elements.|
Add a separate sub Canvas for large ScrollRects.
Turn off pixel perfect for Canvas that wraps a large ScrollRect.
Modify the code to stop ScrollRect when velocity is low.
|33:40||Turn off “Rayast Target” box whenever possible.|
|34:40||Always assign Event Camera property in Canvas component.|
|38:10||Always assign World Camera for world space canvases.|
|38:30||Do not add Graphic Raycaster to non-interactive elements.|
|39:30||Layout components are painfully slow.|
|43:50||Each Layout Group multiplies its cost. Nested Layout Groups are particularly bad.|
|44:10||What marks layout dirty: |
|45:10||Use anchors instead of Layout Group for proportional UIs.|
For a truly dynamic UI write custom code to maintain its layout, you know better how your UI must work. Update UI on demand.
|46:00||When using Object Pool for UI elements:|
On adding to pool disable game object first, only then reparent.
When getting back from pool reparent first, then update data, and only then enable.
|46:50||Disable the Canvas component instead of disabling game object.|
|48:40||Never use Animator for UI elements. Animator dirties elements every frame, even if nothing happens visually.|
Unite Berlin 2018 – Unity’s Evolving Best Practices
|01:30||Default tip: profile everything, trust no one.|
|07:40||String comparison is a pretty complicated topic, so if you use it really heavily make sure to choose the best method for your use case.|
|11:40||Use attribute |
|16:00||Since Unity 5.4 transforms are stored in a contiguous buffers. There is one |
|18:50||TransformChangedDispatch keeps a list of dirty TransformHierarchies pointers. Yes, it operates on a hierarchy level.|
Set parent and position using GameObject.Instantiate(…) arguments.
|20:10||Split your transform hierarchies. Smaller hierarchies are better. More root transforms is better since its checking is jobified.|
|25:40||Batch transform updates before using physics.|
Use flat hierarchies instead of deep ones.
|28:30||FMOD background thread CPU usage is not shown in the profiler. Scroll down to the Audio tab in Unity Profiler for evaluating CPU load by FMOD background audio threads.|
|30:17||Use Vorbis compression on iOS for audio clips.|
|30:20||Avoid having too many Audio Sources put on mute.|
|30:55||Use decompress on load option if you can afford memory overhead. E.g. for short frequent sounds|
Avoid playing lots of compressed clips, especially on mobile
|31:30||FMOD background threads overload can cause framerate stutter, since it has realtime priority and can interrupt the main thread.|
|32:00||Clamp the voice count to save CPU time via |
|35:40||Animator can push work onto worker threads. Therefore it scales better. But has overhead.|
So always test it on the min target hardware.
Consider number of curves in animations.
More cores – better for animator. Animator outperforms Animation on lower amount of animation curves, the more cores/power CPU has.
Use Animation for simple animations.
Use Animator for high curve count clips and complex scenarios.
|38:00||In Animator active state on each layer is evaluated every frame, even if weight = 0. Zero-weight layers – waste of performance.|
|38:50||Additional layer in Animator increases cost by around 20%.|
|39:30||Use the right rig. Humanoid rig is slower than generic rig. Because humanoid runs IK and retargeting calculations.|
|40:30||Pooling: Animator resets state when its GameObject is disabled. Fixed in 2018.1 by adding |
|43:30||Split dynamic and static transforms into separate hierarchies with postprocess scripts that does that split without affecting production flow.|
|45:20||No difference between X root transforms and X additive scenes in terms of how TransformChangedDispatch works.|
|46:20||Use scripts to animate simple UI. Or at least Animation as it has less overhead on start and stop.|
There is an enormous amount of top quality talks from Unite. The ones listed here are my favorites. Share talks you personally like in the comments and leave your opinion on whether such type of posts are helpful or not. And if you like more tips and advice, give my telegram channel a follow: I post interesting stuff I find about game development everyday.