Optimization Performance Unity Tips

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:45Unity serializes everything you have in a scene and prefabs. There are no duplicate data checks and no compression.
07:20All serialized data is included in builds.
08:15AddComponent() is pretty heavy. It spends most of the time looking up the scripting class it must attach, also it spawns a C++ backing class.
13:10Use nested prefabs for repetitive elements.
18:00Move 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:35Pass a parent transform into GameObject.Instantiate(…) method instead of setting parent manually later via transform.parent property setter. Check the next tip for more details.
20:05Since Unity 5.4 transforms are stored in a contiguous 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:35Set transform.hierarchyCapacity beforehand if it is known to avoid reallocations and buffer copying time.
23:55Do not use transforms as “folders” in your scenes.
24:00Do not reparent objects when using the Object Pool pattern unless absolutely necessary. At least in release builds.
26:10Apply 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 the pull model to avoid this issue.
28:10Use Optimize Game Object box on skinned meshes to greatly increase performance by allowing Unity to parallelize the processing of bones and improve loading times since it could reduce the number of transforms in a scene significantly if skinned meshes are used heavily.
33:15Disable the parent Canvas component instead of GameObject. Text component is especially heavy with its OnEnable since UI Text dirties itself in OnEnable. Do not forget to disable MonoBehaviour callbacks like Update() manually in that case.
37:30Merge 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:00Split canvases. Keep dynamic and static elements in separate canvases. Keep a balance between extra draw calls vs the cost of batching.

Unite Europe 2017 – Squeezing Unity: Tips for raising performance



03:00Profile 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:15Reduce the amount of API calls that return an array. Make sure to use a non-allocating equivalent if available.
10:00Know how your data structures work internally. When to use Array, List, Dictionary, HashSet, etc.
13:30You can use double collection when you need to iterate frequently over the collection, but also need key access.
15:50If you need Dictionary with MonoBehaviour or ScriptableObject key, then use id = MonoBehaviour.GetInstanceId() and make it an int-keyed Dictionary using this id as a key.
17:45The 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:00Use 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:00Use fields instead of properties in a hot path.
25:00What happens when Canvas rebuilds its mesh: recalculate layout, regenerate mesh, regenerate materials.
25:15When a UI element is dirty the mesh for it will be regenerated even if the alpha is 0.
26:17Changing the color of a graphic only dirties its vertices. Changing Fill* properties also dirties only vertices. So no Canvas mesh rebuild is needed.
26:40UI elements are sorted by depth, yes, it includes sort() operation call, which is an 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:35Nesting canvases is a good practice, as dirtying a child canvas does not dirty the parent. Child canvases maintain their own geometry and perform their own batching.
29:00When splitting canvases, group elements that are updated simultaneously.
Separate dynamic elements from static ones.
30:15Use 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:40Turn off the “Raycast Target” box whenever possible.
34:40Always assign the Event Camera property in Canvas component.
36:20FindObjectWithTag() is not as bad as FindObjectOfType(). But obviously, the more game objects with tags are indexed, the slower it works.
38:10Always assign World Camera for world space canvases.
38:30Do not add Graphic Raycaster to non-interactive elements.
39:30Layout components are painfully slow.
43:50Each Layout Group multiplies its cost. Nested Layout Groups are particularly bad.
44:10What marks a layout dirty: OnEnable, OnDisable, reparenting (twice: for the new and the old parent), OnDidApplyAnimatorProperties, OnRectTransformDimensionsChanged.
45:10Use 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:00When using Object Pool for UI elements:
On adding to the pool disable the game object first, only then reparent.
When getting back from the pool reparent first, then update data, and only then enable.
46:50Disable a Canvas component instead of disabling a game object.
48:40Never use Animator for UI elements. Animator dirties elements every frame, even if nothing happens visually.

Unite Berlin 2018 – Unity’s Evolving Best Practices



01:30Default tip: profile everything, trust no one.
07:40String 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:40Use attribute [Il2CppSetOption(Option,NullChecks, false)] to remove null checks in the resulting C++ code.
15:10Use Transform.SetPositionAndRotation() to batch transform updates.
16:00Since Unity 5.4 transforms are stored in contiguous buffers. There is one TransformHierarchy structure for each root transform in a scene.
18:50TransformChangedDispatch keeps a list of dirty TransformHierarchies pointers. Yes, it operates on a hierarchy level.
19:30Set Transform.hierarchyCapacity if you know the size of the hierarchy. Use it, especially before mass reparenting operations.
Set parent and position using GameObject.Instantiate(…) arguments.
20:10Split your transform hierarchies. Smaller hierarchies are better. More root transforms are better since their checking is jobified.
24:00Use Physics.SyncTransforms if you need to propagate transform changes to the PhysX system. Physics.AutoSyncTransforms = true, enables it by default, with performance hit of course.
25:40Batch transform updates before using physics.
Use flat hierarchies instead of deep ones.
28:10AudioSource.mute doesn’t prevent computational work. Disable a muted Audio Source.
28:30FMOD 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:17Use Vorbis compression on iOS for audio clips.
30:20Avoid having too many Audio Sources put on mute.
30:55Use 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:30FMOD background threads overload can cause framerate stutter since it has realtime priority and can interrupt the main thread.
32:00Clamp the voice count to save CPU time via
configuration = AudioSettings.GetConfiguration();
configuration.numRealVoices = Mathf.Clamp(configuration.numRealVoices, 1, configuration.numVirtualVoices);
35:40Animator can push work onto worker threads. Therefore it scales better. But has overhead.
So always test it on the min target hardware.
Consider a number of curves in animations.
More cores – better for Animator. Animator outperforms Animation on a 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:00In Animator active state on each layer is evaluated every frame, even if weight = 0. Zero-weight layers – waste of performance.
38:50An additional layer in Animator increases cost by around 20%.
39:30Use the right rig. The humanoid rig is slower than the generic rig. Because humanoid runs IK and retargeting calculations.
40:30Pooling: Animator resets state when its GameObject is disabled. Fixed in 2018.1 by adding Animator.keepControllerStateOnDisable. Useful for pooling, but has a higher memory cost for disabled Animators.
43:30Split dynamic and static transforms into separate hierarchies with postprocess scripts that do that split without affecting production flow.
45:20No difference between X root transforms and X additive scenes in terms of how TransformChangedDispatch works.
46:20Use scripts to animate simple UI. Or at least Animation as it has less overhead on start and stop.

Post Scriptum

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 is 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 every day.