Unite Amsterdam 2017 - Squeezing Unity

Unite Amsterdam 2017 - Squeezing Unity
Foreward, for Slideshare/Youtube
This deck contains slides presented during Unite Amsterdam 2017 and are intended as a
supplement to that talk.
More slides are included in this deck than were presented live. The additional slides are
included inline with the existing material, and primarily include code samples and
additional data to supplement understanding the discussed topics.
In a handful of cases, additional material is added — this is mostly notable in the “Unity
APIs You Cannot Trust” section, where several whole sections were cut for time. These
have been left in as an added bonus for you, the dedicated reader.
Enjoy!
SqueezingUnity
(Yetanotherperformancetalk)
Mark Harkness & Ian Dundore

DeveloperRelationsEngineers
What’rewetalkingabout?
Today’s Menu
• Scurrilous APIs
• Data structures
• Inlining
• Extreme micro-optimization
Today’s Menu
• Scurrilous APIs
• Data structures
• Inlining
• Extreme micro-optimization
• Unity UI
Obligatory Reminder:
PROFILE. THINGS. FIRST.
Unity APIs You Cannot Trust
Particle Systems
• APIs recursively iterate over all child
GameObjects.
• Start, Stop, Pause, Clear, Simulate, etc.
• IsAlive(), with the default argument, is recursive!
Particle Systems (2)
• Recursive calls execute on every child Transform.
• This happens even if none of the children have
ParticleSystem Components!
• Assuming a GameObject with 100 children:
• ParticleSystem.Stop() = 100 calls to GetComponent
Particle Systems
• Solution:
• Make sure to pass false to withChildren param
• Cache a list of ParticleSystems in the hierarchy.
• Iterate over this list manually when calling APIs
Particle Systems: More Sad News
• All calls to Stop and Simulate will cause GC allocs
• Versions: 5.4, 5.5, 5.6, 2017.1
• The C# wrapper code was written to use a closure.
• Fixed in 2017.2.
Particle Systems: Workaround
• ParticleSystem APIs are backed by internal helper
methods.
• Start --> Internal_Start(ParticleSystem)
• Stop —> Internal_Stop(ParticleSystem)
• Simulate —> Internal_Simulate(…)
• Can be addressed via Reflection…
Particle Systems: Function Signatures
private static bool Internal_Stop(ParticleSystem self,
ParticleSystemStopBehavior stopBehavior)
private static bool Internal_Simulate(ParticleSystem self,
float t, bool restart, bool fixedTimeStep)
Arguments have the same meanings as public API.
Classic: Array-returning APIs
Beware APIs that return arrays
• Tenet: When a Unity API returns an array, it is
always a new copy of the array.
• This is for safety reasons.
• Mesh.vertices, Input.touches
• Maxim: Minimize calls to APIs that return arrays.
for(int i = 0; i < Input.touches.Length; ++i)
{
doSomethingWithTouch(Input.touches[i]);
}
Bad: Allocates many times
var touchArray = Input.touches;
for(int i = 0; i < touchArray.Length; ++i) {
doSomethingWithTouch(touchArray[i]);
}
Better: Allocates once
for(int i = 0; i < Input.touchCount; ++i) {
doSomethingWithTouch(Input.GetTouch(i));
}
Best: Doesn’t allocate
A non-exhaustive list
• Input.touches
• Use Input.GetTouch!
• Physics.RaycastAll & other “All” *casts
• Non-allocating Physics APIs have existed since 5.3!
• GetComponents, GetComponentsInChildren
• Versions which accept pre-allocated List<T> objects exist!
Property Name Hashing
Materials?
• When addressing properties on materials &
shaders, use integer property IDs instead of string
property names.
• GetFloat, SetFloat, GetTexture, SetTexture, etc.
• If you pass string properly names, they’re hashed.
Material myMaterial = GetMyMaterialSomehow();
myMaterial.SetFloat(“_SomeProperty”, 100f);
myMaterial.SetTexture(“_SomeTexture”, someTex);
Don’t do this
// During initialization…
int propId_SomeFloat;
Int propId_SomeTexture;
void Awake() {
propId_SomeFloat = Shader.PropertyToID(“_SomeFloat”);
propId_SomeTexture = Shader.PropertyToID(“_SomeTexture”);
}
// … Later
Material myMaterial = GetMyMaterialSomehow();
myMaterial.SetFloat(propId_SomeFloat, 100f);
myMaterial.SetTexture(propId_SomeTexture, someTex);
Materials:The Catch
• Material.color
• Material.mainTexture
• Material.mainTextureOffset
• Material.mainTextureScale
• These properties use the string-argument methods.
Materials: Workaround
• Use the standard methods instead
• Get & cache the property ID yourself
• Material.color
• Material.GetColor, Material.SetColor
• Property name: “_Color”
Materials: Workaround
• Property name: “_MainTex”
• Material.mainTexture
• Material.GetTexture, Material.SetTexture
• Material.mainTextureOffset
• Material.GetTextureOffset, Material.SetTextureOffset
• Material.mainTextureScale
• Material.GetTextureScale, Material.SetTextureScale
My favorite one
• Camera.main
• Calls Object.FindObjectWithTag(“MainCamera”)
• Every single time you access it.
• Remember this one! It’ll be on the homework.
Data Structures
Know how your data structures work!
• Mostly iterating? Use arrays or Lists.
• Mostly adding/removing? Dictionary or Hashset.
• Mostly indexing by key? Dictionary.
• Common problem: iterating over dictionaries & hash sets.
• These are hash tables: high iteration overhead.
What happens when concerns collide?
• Common case: an Update Manager.
• Need low-overhead iteration.
• Need constant-time insertion.
• Need constant-time duplicate checks.
Examine the requirements.
• Need low-overhead iteration.
• Array or List.
• Need constant-time insertion.
• List, Dictionary, HashSet
• Need constant-time duplicate checks.
• Dictionary, HashSet
No single data structure works?
• … so use two.
• Maintain a List for iteration.
• Before changing the List, perform checks on a HashSet
• If removal is a concern, consider a linked list or
intrusively-linked list implementation.
• Downside: Higher memory cost.
Dictionary Quick Tip!
UnityEngine.Object-keyed Dictionaries
• Default comparer uses Object.Equals
• Calls UnityEngine.Object.CompareBaseObjects
• Calls C#’s Object.ReferenceEquals method.
• Small amount of overhead involved.
InstanceID-keyed Dictionaries!
• All UnityEngine.Objects have an Instance ID
• Unique!
• Never changes over lifetime of application!
• Just an integer!
• myObj.GetInstanceID()
public class ThingCaller : MonoBehaviour {



private ThingToCall[] objs;

private int[] ids;



private Dictionary<ThingToCall, int> fooCumulativeObj;

private Dictionary<int, int> fooCumulativeId;

void Start () {
objs = new ThingToCall[NUM_OBJS];
ids = new int[NUM_OBJS];
fooCumulativeObj = new Dictionary<ThingToCall, int>();
fooCumulativeId = new Dictionary<int, int>();
var template = new GameObject();
template.AddComponent<ThingToCall>();
for (int i = 0; i < NUM_OBJS; ++i) {
var newGo = Object.Instantiate(template);
objs[i] = newGo.GetComponent<ThingToCall>();
ids[i] = objs[i].GetInstanceID();
fooCumulativeObj.Add(objs[i], 0);
fooCumulativeId.Add(ids[i], 0);
}
}
void Update () {
ShuffleThings();
for (int i = 0; i < NUM_OBJS; ++i) {
var baseObj = objs[i];
int val = fooCumulativeObj[baseObj];
val += objs[i].SomeMethod();
fooCumulativeObj[baseObj] = val;
}
}
void Update () {
ShuffleThings();
for (int i = 0; i < NUM_OBJS; ++i) {
var baseObj = objs[i];
var baseId = baseObj.GetInstanceID();
int val = fooCumulativeId[baseId];
val += objs[i].SomeMethod();
fooCumulativeId[baseId] = val;
}
}
void Update () {
ShuffleThings();
for (int i = 0; i < NUM_OBJS; ++i) {
var baseObj = objs[i];
var baseId = ids[i];
int val = fooCumulativeId[baseId];
val += objs[i].SomeMethod();
fooCumulativeId[baseId] = val;
}
}
How will it perform?
Cached IDs are best, duh.
(msec) OSX, Mono iPad 2, IL2CPP
Object key 0.15 1.33
Int key
(uncached)
0.19 0.91
Int key
(cached)
0.06 0.54
Why?
• Calling GetInstanceID invokes unsafe code.
• Mono’s JIT compiler optimizes the loop poorly.
• Less of a problem under IL2CPP.
• C#’s Integer-keyed Dictionary is highly optimized.
When to use this
• Do not do this for all Object-keyed Dictionaries!
• Reserve for key data structures
• Dictionaries that are indexed hundreds or
thousands of times per frame.
Recursion
public class TreeSearcher : MonoBehaviour {

private const int NUM_NUMBERS = 1001;

private int[] numbers;



private BSTree tree;



void Start () {

tree = new BSTree();

numbers = new int[NUM_NUMBERS];

for (int i = 0; i < NUM_NUMBERS; ++i) {

numbers[i] = i;

tree.Add(i);

}
tree.Balance();
}
void Update() {
int numsFound = 0;
ShuffleNumbers();
for(int i = 0; i < NUM_NUMBERS; ++i) {
BSTreeNode n = tree.FindRecursive(numbers[i]);
if(n != null) {
numsFound += 1;
}
}
// … log out numsFound to prevent optimization
}
void Update() {
int numsFound = 0;
ShuffleNumbers();
for(int i = 0; i < NUM_NUMBERS; ++i) {
BSTreeNode n = tree.FindStack(numbers[i]);
if(n != null) {
numsFound += 1;
}
}
// … log out numsFound to prevent optimization
}
Searching a thousand-node BSTx 1000
(msec) OSX, Mono iPad 2, IL2CPP Editor
Recursive 2.9 6.9 13.1
Stack 1.9 6.5 13.1
Interpreting these results.
• On JIT platforms, recursion overhead is high.
• On cross-compiled platforms, the difference is
smaller but still exists.
• Editor suppresses JIT optimizations and emits
additional code to support debugging.
Inlining
Inlining?
• Hypothetically the simplest way to eliminate
method-call overhead.
• Hypothetically.
• In reality, the Unity C# compiler is very averse to
inlining.
public class Inliner : MonoBehaviour {
public const int NUM_RUNS = 100;
public string searchWord;
public TextAsset originalText;
private string[] lines;
void OnEnable() {
lines = (originalText != null)
? originalText.text.Split(‘n')
: new string[0];
}
private int TestInlining() {
int numLines = lines.Length;
int count = 0;
for (int i = 0; i < NUM_RUNS; ++i)
{
count = 0;
for (int lineIdx = 0; lineIdx < numLines; ++lineIdx)
{
count +=
FindNumberOfInstancesOfWord(
lines[lineIdx],
searchWord);
}
}
}
private int FindNumberOfInstancesOfWord(string text,
string word) {
int idx, count, textLen, wordLen;
count = idx = 0;
textLen = text.Length;
wordLen = word.Length;
while(idx >= 0 && idx < textLen) {
idx = text.IndexOf(word, idx,
System.StringComparison.Ordinal);
if (idx < 0)
break;
idx += wordLen;
count += 1;
}
return count;
}
[MethodImplAttribute(MethodImplOptions.NoInlining)]
private int FindNumberOfInstancesOfWord(string text,
string word) {
// … goes over line with String.IndexOf
// … counts # of times word appears
// … and returns the result
}
[MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
private int FindNumberOfInstancesOfWord(string text,
string word) {
// … goes over line with String.IndexOf
// … counts # of times word appears
// … and returns the result
}
New in the 4.6 runtime!
private int TestInliningManual() {
int numLines = lines.Length;
int count = 0;
for (int i = 0; i < INLINE_LIMIT; ++i) {
count = 0;
for (int lineIdx = 0; lineIdx < numLines; ++lineIdx) {
int idx = 0;
int textLen = lines[lineIdx].Length;
int wordLen = searchWord.Length;
while (idx >= 0 && idx < textLen) {
idx = lines[lineIdx].IndexOf(searchWord, idx,
System.StringComparison.Ordinal);
if (idx < 0)
break;
idx += wordLen;
count += 1;
}
}
}
return count;
}
Copy-pasted function body
Inlining!
(msec) OSX, Mono
iPad 2,
IL2CPP
3.5,
No inlining
5.2 26.0
3.5,
Manual
4.6 23.8
4.6,
Aggressive
5.6 26.5
4.6,
Manual
5.4 24.5
Results?
• Manual inlining is useful for small methods on extremely
hot paths.
• Maintenance nightmare. Use sparingly!
• AggressiveInlining mostly eliminates the difference in
4.6, and is much easier to maintain.
• AggressiveInlining is not yet implemented for IL2CPP.
• On the roadmap though.
Other places where this is relevant…
• Trivial properties generate methods
• Yes, these are method calls!
• public int MyProperty { get; set; }
• Convert to public fields in hot code-paths.
• public int MyProperty;
Unite Amsterdam 2017 - Squeezing Unity
UnityUI
TheAbsoluteBasics
• Canvases generate meshes & draw them
• Drawing happens once per frame
• Generation happens when “something changes”
• “Something changes” = “1 or more UI elements change”
• Change one UI element, dirty the whole canvas.
• Yes, one.
What’s a rebuild?Theoretically…
• Three steps:
• Recalculate layouts of auto-layouted elements
• Regenerate meshes for all enabled elements
• Yes, meshes are still created when alpha=0!
• Regenerate materials to help batch meshes
What’s a rebuild? In Reality…
• Usually, all systems are dirtied
instead of individually.
• Notable exceptions:
• Color property
• fill* properties on Image
After all that…
• Canvas takes meshes, divides them into batches.
• Sorts the meshes by depth (hierarchy order)
• Yes, this involves a sort() operation!
• All UI is transparent. No matter what.
• Overdraw is a problem!
Trivially batchable.
Colors represent different materials.
Not batchable!
Red quad is interposed
Colors represent different materials.
This is batchable!
Red quad is overlaid
Colors represent different materials.
The Vicious Cycle
• Sort means performance drops faster-than-linear
as number of drawable elements rises.
• Higher number of elements means a higher
chance any given element will change.
Slice up your canvases!
• Add more canvases.
• Each canvas owns its own set of UI elements.
• Elements on different canvases will not batch.
• Main tool for constraining size of UI batches.
Nesting Canvases
• Canvases can be created within other canvases.
• Child canvases inherit rendering settings.
• Maintain own geometry.
• Perform own batching.
General Guidelines
• Subdivide big canvases
• Group elements that are updated simultaneously
• Separate dynamic elements from static ones
• Backgrounds, static labels, etc.
• Spinners, throbbers, blinking carets, etc.
Scroll Rect,
1000 Items
25ms just to scroll a list!
Unite Amsterdam 2017 - Squeezing Unity
Unite Amsterdam 2017 - Squeezing Unity
Down to “only” 5ms
Wherefromhere?
• Pool child UI elements in Scroll Rect
• Minimizes cost of rebuilds
• Modify Scroll Rect code
• Stop scrolling when velocity gets very small
• Right now it will move items 0.0001 pixels…
GraphicRaycaster
What’s it do?
• Translates screen/touch input into Events.
• Sends events to interested UI elements.
• Need one on every Canvas that requires input.
• Including subcanvases.
Not really a “raycaster”…
• With default settings, only tests UI graphics.
• Performs point-rectangle intersection check for
each RectTransform marked interactive.
• Yes, just a simple loop.
First tip!
• Turn off “Raycast Target” where possible.
• Directly Reduces number of per-frame checks.
2D/3D Geometry?
• “Worldspace” or “Screen Space - Camera” canvas
• Blocking mask can select whether to cast
against 2D/3D geometry
• Will then perform a 2D/3D raycast outwards from
the Canvas’ event camera
• Limited to camera’s visible frustum
… “event camera”?
Unite Amsterdam 2017 - Squeezing Unity
What if that isn’t set?
public override Camera eventCamera
{
get
{
if (canvas.renderMode ==
RenderMode.ScreenSpaceOverlay
|| (canvas.renderMode ==
RenderMode.ScreenSpaceCamera && canvas.worldCamera == null))
return null;
return canvas.worldCamera != null ?
canvas.worldCamera : Camera.main;
}
}
Unite Amsterdam 2017 - Squeezing Unity
eventCamera is accessed 7-10
times per frame for Raycasting.
Per World-Space Canvas.
Camera.main
=
FindObjectWithTag
10 Canvases + X tagged objects
Unassigned Assigned
10 0.07 0.06
100 0.13 0.07
1000 0.85 0.07
{X
Timings are in milliseconds
What can you do about this?
• Avoid use of Camera.main
• Cache references to Cameras.
• Create a system to track the main camera.
• Matters mostly in per-frame code
What about in Unity UI?
• “World Space” canvas?
• Always assign a World Camera
• Minimize number of Graphic Raycasters
• Do not add them to non-interactive UIs!
Layouts!
Setting dirty flags should be cheap, right?
• Layout works on a “dirty flag” system.
• When an element changes in a way that would
invalidate the surrounding layout, it must notify
the layout system.
• Layout system = Layout Groups
Wait but…
• Layout Groups are components too!
• Vertical Layout Group, Grid Layout Group…
• Scroll Rect
• They are always Components on parent
GameObjects of Layout Elements.
How do Layout Elements know
which Layout Group to dirty?
Walking the code…
• https://bitbucket.org/Unity-Technologies/ui/
• Graphic.cs :: SetLayoutDirty
• Base class for all drawable UI elements
• UI Text, UI Image, etc.
public virtual void SetLayoutDirty()
{
if (!IsActive())
return;
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
if (m_OnDirtyLayoutCallback != null)
m_OnDirtyLayoutCallback();
}
public static void MarkLayoutForRebuild(RectTransform rect)
{
// ...
var comps = ListPool<Component>.Get();
RectTransform layoutRoot = rect;
while (true)
{
var parent = layoutRoot.parent as RectTransform;
if (!ValidLayoutGroup(parent, comps))
break;
layoutRoot = parent;
}
// ...
}
Immediately, we see something…
• MarkLayoutForRebuild searches for the Layout
Group closest to the root of the hierarchy.
• Stops if it doesn’t find a Layout Group.
• Number of checks rises linearly with number of
consecutively nested layout groups.
What’s ValidLayoutGroup do?
private static bool ValidLayoutGroup
(RectTransform parent, List<Component> comps)
{
if (parent == null)
return false;
parent.GetComponents(typeof(ILayoutGroup), comps);
StripDisabledBehavioursFromList(comps);
var validCount = comps.Count > 0;
return validCount;
}
Unite Amsterdam 2017 - Squeezing Unity
static void StripDisabledBehavioursFromList
(List<Component> components)
{
components.RemoveAll(e =>
e is Behaviour &&
!((Behaviour)e).isActiveAndEnabled);
}
LINQ + List Modification in the hot path…
Takeaways
• Every UI element that tries to dirty its layout will
perform at least 1 GetComponents call.
• Each time it marks its layout dirty.
• Each layout group multiplies this cost.
• Nested layout groups are particularly bad.
Things that mark layouts dirty…
• OnEnable & OnDisable
• Reparenting
• Twice! Once for old parent, once for new parent
• OnDidApplyAnimationProperties
• OnRectTransformDimensionsChanged
OnRectTransformDimensionsChanged?
• Change Canvas Scaling Factor, Camera
• Sent to all elements on Canvas
• Change Transform anchors, position
• Sent to all child RectTransforms
• All the way to the bottom of the hierarchy!
What else?
• Images
• Changing sprite or overrideSprite
• Text
• Changing most properties
• Particularly: text, alignment, fontSize
Yeah, EVERYTHING dirties Layout!
Enough. Let’s have some solutions.
• Avoid Layout Groups wherever possible.
• Use anchors for proportional layouts.
• On hot UIs with dynamic number of elements,
consider writing own code to calculate layouts.
• Update this on-demand instead of with every
single change.
Pooling UI objects?
• Maximize number of dirty calls that will no-op.
• Disable first, then reparent into pool.
• Removing from pool?
• Reparent first, then update data, then enable.
Hiding a canvas?
• Consider disabling the Canvas component.
• Stops drawing the Canvas
• Does not trigger expensive OnDisable/
OnEnable callbacks on UI hierarchy.
• Be careful to disable child components that run
expensive per-frame code!
In extreme cases…
• Build your own UnityEngine.UI.dll from source.
• Eliminate LayoutRebuilder.
• This is a lot of work.
Animators on your UI?
DON’T
Beware of Animators on UI!
• Animators will dirty their elements every frame.
• Even if value in animation doesn’t change!
• Animators have no no-op checks.
• Animators are OK on things that always change
• … or use your own code or tweening system
Unite Amsterdam 2017 - Squeezing Unity
Thank you!
1 of 124

Recommended

ChatGPT and the Future of Work - Clark Boyd by
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
25.3K views69 slides
Getting into the tech field. what next by
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
6K views22 slides
Google's Just Not That Into You: Understanding Core Updates & Search Intent by
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
6.5K views99 slides
How to have difficult conversations by
How to have difficult conversations How to have difficult conversations
How to have difficult conversations Rajiv Jayarajah, MAppComm, ACC
5.2K views19 slides
Introduction to Data Science by
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data ScienceChristy Abraham Joy
82.4K views51 slides
Time Management & Productivity - Best Practices by
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
169.7K views42 slides

More Related Content

Recently uploaded

Programming Field by
Programming FieldProgramming Field
Programming Fieldthehardtechnology
5 views9 slides
Unlocking the Power of AI in Product Management - A Comprehensive Guide for P... by
Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...
Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...NimaTorabi2
15 views17 slides
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI... by
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Marc Müller
42 views83 slides
Advanced API Mocking Techniques by
Advanced API Mocking TechniquesAdvanced API Mocking Techniques
Advanced API Mocking TechniquesDimpy Adhikary
23 views11 slides
Gen Apps on Google Cloud PaLM2 and Codey APIs in Action by
Gen Apps on Google Cloud PaLM2 and Codey APIs in ActionGen Apps on Google Cloud PaLM2 and Codey APIs in Action
Gen Apps on Google Cloud PaLM2 and Codey APIs in ActionMárton Kodok
11 views55 slides
Quality Engineer: A Day in the Life by
Quality Engineer: A Day in the LifeQuality Engineer: A Day in the Life
Quality Engineer: A Day in the LifeJohn Valentino
6 views18 slides

Recently uploaded(20)

Unlocking the Power of AI in Product Management - A Comprehensive Guide for P... by NimaTorabi2
Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...
Unlocking the Power of AI in Product Management - A Comprehensive Guide for P...
NimaTorabi215 views
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI... by Marc Müller
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Dev-Cloud Conference 2023 - Continuous Deployment Showdown: Traditionelles CI...
Marc Müller42 views
Advanced API Mocking Techniques by Dimpy Adhikary
Advanced API Mocking TechniquesAdvanced API Mocking Techniques
Advanced API Mocking Techniques
Dimpy Adhikary23 views
Gen Apps on Google Cloud PaLM2 and Codey APIs in Action by Márton Kodok
Gen Apps on Google Cloud PaLM2 and Codey APIs in ActionGen Apps on Google Cloud PaLM2 and Codey APIs in Action
Gen Apps on Google Cloud PaLM2 and Codey APIs in Action
Márton Kodok11 views
Quality Engineer: A Day in the Life by John Valentino
Quality Engineer: A Day in the LifeQuality Engineer: A Day in the Life
Quality Engineer: A Day in the Life
John Valentino6 views
FOSSLight Community Day 2023-11-30 by Shane Coughlan
FOSSLight Community Day 2023-11-30FOSSLight Community Day 2023-11-30
FOSSLight Community Day 2023-11-30
Shane Coughlan5 views
Ports-and-Adapters Architecture for Embedded HMI by Burkhard Stubert
Ports-and-Adapters Architecture for Embedded HMIPorts-and-Adapters Architecture for Embedded HMI
Ports-and-Adapters Architecture for Embedded HMI
Burkhard Stubert21 views
Sprint 226 by ManageIQ
Sprint 226Sprint 226
Sprint 226
ManageIQ8 views
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with... by sparkfabrik
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...
20231129 - Platform @ localhost 2023 - Application-driven infrastructure with...
sparkfabrik8 views
Dapr Unleashed: Accelerating Microservice Development by Miroslav Janeski
Dapr Unleashed: Accelerating Microservice DevelopmentDapr Unleashed: Accelerating Microservice Development
Dapr Unleashed: Accelerating Microservice Development
Miroslav Janeski12 views
Bootstrapping vs Venture Capital.pptx by Zeljko Svedic
Bootstrapping vs Venture Capital.pptxBootstrapping vs Venture Capital.pptx
Bootstrapping vs Venture Capital.pptx
Zeljko Svedic12 views
JioEngage_Presentation.pptx by admin125455
JioEngage_Presentation.pptxJioEngage_Presentation.pptx
JioEngage_Presentation.pptx
admin1254556 views
Software evolution understanding: Automatic extraction of software identifier... by Ra'Fat Al-Msie'deen
Software evolution understanding: Automatic extraction of software identifier...Software evolution understanding: Automatic extraction of software identifier...
Software evolution understanding: Automatic extraction of software identifier...

Featured

Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present... by
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Applitools
55.5K views138 slides
12 Ways to Increase Your Influence at Work by
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at WorkGetSmarter
401.7K views64 slides
ChatGPT webinar slides by
ChatGPT webinar slidesChatGPT webinar slides
ChatGPT webinar slidesAlireza Esmikhani
30.4K views36 slides
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G... by
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...DevGAMM Conference
3.6K views12 slides
Barbie - Brand Strategy Presentation by
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy PresentationErica Santiago
25.1K views46 slides

Featured(20)

Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present... by Applitools
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Applitools55.5K views
12 Ways to Increase Your Influence at Work by GetSmarter
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
GetSmarter401.7K views
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G... by DevGAMM Conference
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
DevGAMM Conference3.6K views
Barbie - Brand Strategy Presentation by Erica Santiago
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy Presentation
Erica Santiago25.1K views
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well by Saba Software
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them wellGood Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Saba Software25.2K views
Introduction to C Programming Language by Simplilearn
Introduction to C Programming LanguageIntroduction to C Programming Language
Introduction to C Programming Language
Simplilearn8.4K views
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr... by Palo Alto Software
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...
Palo Alto Software88.4K views
9 Tips for a Work-free Vacation by Weekdone.com
9 Tips for a Work-free Vacation9 Tips for a Work-free Vacation
9 Tips for a Work-free Vacation
Weekdone.com7.2K views
How to Map Your Future by SlideShop.com
How to Map Your FutureHow to Map Your Future
How to Map Your Future
SlideShop.com275.1K views
Beyond Pride: Making Digital Marketing & SEO Authentically LGBTQ+ Inclusive -... by AccuraCast
Beyond Pride: Making Digital Marketing & SEO Authentically LGBTQ+ Inclusive -...Beyond Pride: Making Digital Marketing & SEO Authentically LGBTQ+ Inclusive -...
Beyond Pride: Making Digital Marketing & SEO Authentically LGBTQ+ Inclusive -...
AccuraCast3.4K views
Exploring ChatGPT for Effective Teaching and Learning.pptx by Stan Skrabut, Ed.D.
Exploring ChatGPT for Effective Teaching and Learning.pptxExploring ChatGPT for Effective Teaching and Learning.pptx
Exploring ChatGPT for Effective Teaching and Learning.pptx
Stan Skrabut, Ed.D.57.7K views
How to train your robot (with Deep Reinforcement Learning) by Lucas García, PhD
How to train your robot (with Deep Reinforcement Learning)How to train your robot (with Deep Reinforcement Learning)
How to train your robot (with Deep Reinforcement Learning)
Lucas García, PhD42.5K views
4 Strategies to Renew Your Career Passion by Daniel Goleman
4 Strategies to Renew Your Career Passion4 Strategies to Renew Your Career Passion
4 Strategies to Renew Your Career Passion
Daniel Goleman122K views
The Student's Guide to LinkedIn by LinkedIn
The Student's Guide to LinkedInThe Student's Guide to LinkedIn
The Student's Guide to LinkedIn
LinkedIn88K views
Different Roles in Machine Learning Career by Intellipaat
Different Roles in Machine Learning CareerDifferent Roles in Machine Learning Career
Different Roles in Machine Learning Career
Intellipaat12.4K views
Defining a Tech Project Vision in Eight Quick Steps pdf by TechSoup
Defining a Tech Project Vision in Eight Quick Steps pdfDefining a Tech Project Vision in Eight Quick Steps pdf
Defining a Tech Project Vision in Eight Quick Steps pdf
TechSoup 9.7K views

Unite Amsterdam 2017 - Squeezing Unity

  • 2. Foreward, for Slideshare/Youtube This deck contains slides presented during Unite Amsterdam 2017 and are intended as a supplement to that talk. More slides are included in this deck than were presented live. The additional slides are included inline with the existing material, and primarily include code samples and additional data to supplement understanding the discussed topics. In a handful of cases, additional material is added — this is mostly notable in the “Unity APIs You Cannot Trust” section, where several whole sections were cut for time. These have been left in as an added bonus for you, the dedicated reader. Enjoy!
  • 4. Mark Harkness & Ian Dundore
 DeveloperRelationsEngineers
  • 6. Today’s Menu • Scurrilous APIs • Data structures • Inlining • Extreme micro-optimization
  • 7. Today’s Menu • Scurrilous APIs • Data structures • Inlining • Extreme micro-optimization • Unity UI
  • 9. Unity APIs You Cannot Trust
  • 10. Particle Systems • APIs recursively iterate over all child GameObjects. • Start, Stop, Pause, Clear, Simulate, etc. • IsAlive(), with the default argument, is recursive!
  • 11. Particle Systems (2) • Recursive calls execute on every child Transform. • This happens even if none of the children have ParticleSystem Components! • Assuming a GameObject with 100 children: • ParticleSystem.Stop() = 100 calls to GetComponent
  • 12. Particle Systems • Solution: • Make sure to pass false to withChildren param • Cache a list of ParticleSystems in the hierarchy. • Iterate over this list manually when calling APIs
  • 13. Particle Systems: More Sad News • All calls to Stop and Simulate will cause GC allocs • Versions: 5.4, 5.5, 5.6, 2017.1 • The C# wrapper code was written to use a closure. • Fixed in 2017.2.
  • 14. Particle Systems: Workaround • ParticleSystem APIs are backed by internal helper methods. • Start --> Internal_Start(ParticleSystem) • Stop —> Internal_Stop(ParticleSystem) • Simulate —> Internal_Simulate(…) • Can be addressed via Reflection…
  • 15. Particle Systems: Function Signatures private static bool Internal_Stop(ParticleSystem self, ParticleSystemStopBehavior stopBehavior) private static bool Internal_Simulate(ParticleSystem self, float t, bool restart, bool fixedTimeStep) Arguments have the same meanings as public API.
  • 17. Beware APIs that return arrays • Tenet: When a Unity API returns an array, it is always a new copy of the array. • This is for safety reasons. • Mesh.vertices, Input.touches • Maxim: Minimize calls to APIs that return arrays.
  • 18. for(int i = 0; i < Input.touches.Length; ++i) { doSomethingWithTouch(Input.touches[i]); } Bad: Allocates many times
  • 19. var touchArray = Input.touches; for(int i = 0; i < touchArray.Length; ++i) { doSomethingWithTouch(touchArray[i]); } Better: Allocates once
  • 20. for(int i = 0; i < Input.touchCount; ++i) { doSomethingWithTouch(Input.GetTouch(i)); } Best: Doesn’t allocate
  • 21. A non-exhaustive list • Input.touches • Use Input.GetTouch! • Physics.RaycastAll & other “All” *casts • Non-allocating Physics APIs have existed since 5.3! • GetComponents, GetComponentsInChildren • Versions which accept pre-allocated List<T> objects exist!
  • 23. Materials? • When addressing properties on materials & shaders, use integer property IDs instead of string property names. • GetFloat, SetFloat, GetTexture, SetTexture, etc. • If you pass string properly names, they’re hashed.
  • 24. Material myMaterial = GetMyMaterialSomehow(); myMaterial.SetFloat(“_SomeProperty”, 100f); myMaterial.SetTexture(“_SomeTexture”, someTex); Don’t do this
  • 25. // During initialization… int propId_SomeFloat; Int propId_SomeTexture; void Awake() { propId_SomeFloat = Shader.PropertyToID(“_SomeFloat”); propId_SomeTexture = Shader.PropertyToID(“_SomeTexture”); } // … Later Material myMaterial = GetMyMaterialSomehow(); myMaterial.SetFloat(propId_SomeFloat, 100f); myMaterial.SetTexture(propId_SomeTexture, someTex);
  • 26. Materials:The Catch • Material.color • Material.mainTexture • Material.mainTextureOffset • Material.mainTextureScale • These properties use the string-argument methods.
  • 27. Materials: Workaround • Use the standard methods instead • Get & cache the property ID yourself • Material.color • Material.GetColor, Material.SetColor • Property name: “_Color”
  • 28. Materials: Workaround • Property name: “_MainTex” • Material.mainTexture • Material.GetTexture, Material.SetTexture • Material.mainTextureOffset • Material.GetTextureOffset, Material.SetTextureOffset • Material.mainTextureScale • Material.GetTextureScale, Material.SetTextureScale
  • 29. My favorite one • Camera.main • Calls Object.FindObjectWithTag(“MainCamera”) • Every single time you access it. • Remember this one! It’ll be on the homework.
  • 31. Know how your data structures work! • Mostly iterating? Use arrays or Lists. • Mostly adding/removing? Dictionary or Hashset. • Mostly indexing by key? Dictionary. • Common problem: iterating over dictionaries & hash sets. • These are hash tables: high iteration overhead.
  • 32. What happens when concerns collide? • Common case: an Update Manager. • Need low-overhead iteration. • Need constant-time insertion. • Need constant-time duplicate checks.
  • 33. Examine the requirements. • Need low-overhead iteration. • Array or List. • Need constant-time insertion. • List, Dictionary, HashSet • Need constant-time duplicate checks. • Dictionary, HashSet
  • 34. No single data structure works? • … so use two. • Maintain a List for iteration. • Before changing the List, perform checks on a HashSet • If removal is a concern, consider a linked list or intrusively-linked list implementation. • Downside: Higher memory cost.
  • 36. UnityEngine.Object-keyed Dictionaries • Default comparer uses Object.Equals • Calls UnityEngine.Object.CompareBaseObjects • Calls C#’s Object.ReferenceEquals method. • Small amount of overhead involved.
  • 37. InstanceID-keyed Dictionaries! • All UnityEngine.Objects have an Instance ID • Unique! • Never changes over lifetime of application! • Just an integer! • myObj.GetInstanceID()
  • 38. public class ThingCaller : MonoBehaviour {
 
 private ThingToCall[] objs;
 private int[] ids;
 
 private Dictionary<ThingToCall, int> fooCumulativeObj;
 private Dictionary<int, int> fooCumulativeId;

  • 39. void Start () { objs = new ThingToCall[NUM_OBJS]; ids = new int[NUM_OBJS]; fooCumulativeObj = new Dictionary<ThingToCall, int>(); fooCumulativeId = new Dictionary<int, int>(); var template = new GameObject(); template.AddComponent<ThingToCall>(); for (int i = 0; i < NUM_OBJS; ++i) { var newGo = Object.Instantiate(template); objs[i] = newGo.GetComponent<ThingToCall>(); ids[i] = objs[i].GetInstanceID(); fooCumulativeObj.Add(objs[i], 0); fooCumulativeId.Add(ids[i], 0); } }
  • 40. void Update () { ShuffleThings(); for (int i = 0; i < NUM_OBJS; ++i) { var baseObj = objs[i]; int val = fooCumulativeObj[baseObj]; val += objs[i].SomeMethod(); fooCumulativeObj[baseObj] = val; } }
  • 41. void Update () { ShuffleThings(); for (int i = 0; i < NUM_OBJS; ++i) { var baseObj = objs[i]; var baseId = baseObj.GetInstanceID(); int val = fooCumulativeId[baseId]; val += objs[i].SomeMethod(); fooCumulativeId[baseId] = val; } }
  • 42. void Update () { ShuffleThings(); for (int i = 0; i < NUM_OBJS; ++i) { var baseObj = objs[i]; var baseId = ids[i]; int val = fooCumulativeId[baseId]; val += objs[i].SomeMethod(); fooCumulativeId[baseId] = val; } }
  • 43. How will it perform?
  • 44. Cached IDs are best, duh. (msec) OSX, Mono iPad 2, IL2CPP Object key 0.15 1.33 Int key (uncached) 0.19 0.91 Int key (cached) 0.06 0.54
  • 45. Why? • Calling GetInstanceID invokes unsafe code. • Mono’s JIT compiler optimizes the loop poorly. • Less of a problem under IL2CPP. • C#’s Integer-keyed Dictionary is highly optimized.
  • 46. When to use this • Do not do this for all Object-keyed Dictionaries! • Reserve for key data structures • Dictionaries that are indexed hundreds or thousands of times per frame.
  • 48. public class TreeSearcher : MonoBehaviour {
 private const int NUM_NUMBERS = 1001;
 private int[] numbers;
 
 private BSTree tree;
 
 void Start () {
 tree = new BSTree();
 numbers = new int[NUM_NUMBERS];
 for (int i = 0; i < NUM_NUMBERS; ++i) {
 numbers[i] = i;
 tree.Add(i);
 } tree.Balance(); }
  • 49. void Update() { int numsFound = 0; ShuffleNumbers(); for(int i = 0; i < NUM_NUMBERS; ++i) { BSTreeNode n = tree.FindRecursive(numbers[i]); if(n != null) { numsFound += 1; } } // … log out numsFound to prevent optimization }
  • 50. void Update() { int numsFound = 0; ShuffleNumbers(); for(int i = 0; i < NUM_NUMBERS; ++i) { BSTreeNode n = tree.FindStack(numbers[i]); if(n != null) { numsFound += 1; } } // … log out numsFound to prevent optimization }
  • 51. Searching a thousand-node BSTx 1000 (msec) OSX, Mono iPad 2, IL2CPP Editor Recursive 2.9 6.9 13.1 Stack 1.9 6.5 13.1
  • 52. Interpreting these results. • On JIT platforms, recursion overhead is high. • On cross-compiled platforms, the difference is smaller but still exists. • Editor suppresses JIT optimizations and emits additional code to support debugging.
  • 54. Inlining? • Hypothetically the simplest way to eliminate method-call overhead. • Hypothetically. • In reality, the Unity C# compiler is very averse to inlining.
  • 55. public class Inliner : MonoBehaviour { public const int NUM_RUNS = 100; public string searchWord; public TextAsset originalText; private string[] lines; void OnEnable() { lines = (originalText != null) ? originalText.text.Split(‘n') : new string[0]; }
  • 56. private int TestInlining() { int numLines = lines.Length; int count = 0; for (int i = 0; i < NUM_RUNS; ++i) { count = 0; for (int lineIdx = 0; lineIdx < numLines; ++lineIdx) { count += FindNumberOfInstancesOfWord( lines[lineIdx], searchWord); } } }
  • 57. private int FindNumberOfInstancesOfWord(string text, string word) { int idx, count, textLen, wordLen; count = idx = 0; textLen = text.Length; wordLen = word.Length; while(idx >= 0 && idx < textLen) { idx = text.IndexOf(word, idx, System.StringComparison.Ordinal); if (idx < 0) break; idx += wordLen; count += 1; } return count; }
  • 58. [MethodImplAttribute(MethodImplOptions.NoInlining)] private int FindNumberOfInstancesOfWord(string text, string word) { // … goes over line with String.IndexOf // … counts # of times word appears // … and returns the result }
  • 59. [MethodImplAttribute(MethodImplOptions.AggressiveInlining)] private int FindNumberOfInstancesOfWord(string text, string word) { // … goes over line with String.IndexOf // … counts # of times word appears // … and returns the result } New in the 4.6 runtime!
  • 60. private int TestInliningManual() { int numLines = lines.Length; int count = 0; for (int i = 0; i < INLINE_LIMIT; ++i) { count = 0; for (int lineIdx = 0; lineIdx < numLines; ++lineIdx) { int idx = 0; int textLen = lines[lineIdx].Length; int wordLen = searchWord.Length; while (idx >= 0 && idx < textLen) { idx = lines[lineIdx].IndexOf(searchWord, idx, System.StringComparison.Ordinal); if (idx < 0) break; idx += wordLen; count += 1; } } } return count; } Copy-pasted function body
  • 61. Inlining! (msec) OSX, Mono iPad 2, IL2CPP 3.5, No inlining 5.2 26.0 3.5, Manual 4.6 23.8 4.6, Aggressive 5.6 26.5 4.6, Manual 5.4 24.5
  • 62. Results? • Manual inlining is useful for small methods on extremely hot paths. • Maintenance nightmare. Use sparingly! • AggressiveInlining mostly eliminates the difference in 4.6, and is much easier to maintain. • AggressiveInlining is not yet implemented for IL2CPP. • On the roadmap though.
  • 63. Other places where this is relevant… • Trivial properties generate methods • Yes, these are method calls! • public int MyProperty { get; set; } • Convert to public fields in hot code-paths. • public int MyProperty;
  • 66. TheAbsoluteBasics • Canvases generate meshes & draw them • Drawing happens once per frame • Generation happens when “something changes” • “Something changes” = “1 or more UI elements change” • Change one UI element, dirty the whole canvas. • Yes, one.
  • 67. What’s a rebuild?Theoretically… • Three steps: • Recalculate layouts of auto-layouted elements • Regenerate meshes for all enabled elements • Yes, meshes are still created when alpha=0! • Regenerate materials to help batch meshes
  • 68. What’s a rebuild? In Reality… • Usually, all systems are dirtied instead of individually. • Notable exceptions: • Color property • fill* properties on Image
  • 69. After all that… • Canvas takes meshes, divides them into batches. • Sorts the meshes by depth (hierarchy order) • Yes, this involves a sort() operation! • All UI is transparent. No matter what. • Overdraw is a problem!
  • 71. Not batchable! Red quad is interposed Colors represent different materials.
  • 72. This is batchable! Red quad is overlaid Colors represent different materials.
  • 73. The Vicious Cycle • Sort means performance drops faster-than-linear as number of drawable elements rises. • Higher number of elements means a higher chance any given element will change.
  • 74. Slice up your canvases! • Add more canvases. • Each canvas owns its own set of UI elements. • Elements on different canvases will not batch. • Main tool for constraining size of UI batches.
  • 75. Nesting Canvases • Canvases can be created within other canvases. • Child canvases inherit rendering settings. • Maintain own geometry. • Perform own batching.
  • 76. General Guidelines • Subdivide big canvases • Group elements that are updated simultaneously • Separate dynamic elements from static ones • Backgrounds, static labels, etc. • Spinners, throbbers, blinking carets, etc.
  • 78. 25ms just to scroll a list!
  • 82. Wherefromhere? • Pool child UI elements in Scroll Rect • Minimizes cost of rebuilds • Modify Scroll Rect code • Stop scrolling when velocity gets very small • Right now it will move items 0.0001 pixels…
  • 84. What’s it do? • Translates screen/touch input into Events. • Sends events to interested UI elements. • Need one on every Canvas that requires input. • Including subcanvases.
  • 85. Not really a “raycaster”… • With default settings, only tests UI graphics. • Performs point-rectangle intersection check for each RectTransform marked interactive. • Yes, just a simple loop.
  • 86. First tip! • Turn off “Raycast Target” where possible. • Directly Reduces number of per-frame checks.
  • 87. 2D/3D Geometry? • “Worldspace” or “Screen Space - Camera” canvas • Blocking mask can select whether to cast against 2D/3D geometry • Will then perform a 2D/3D raycast outwards from the Canvas’ event camera • Limited to camera’s visible frustum
  • 90. What if that isn’t set?
  • 91. public override Camera eventCamera { get { if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || (canvas.renderMode == RenderMode.ScreenSpaceCamera && canvas.worldCamera == null)) return null; return canvas.worldCamera != null ? canvas.worldCamera : Camera.main; } }
  • 93. eventCamera is accessed 7-10 times per frame for Raycasting. Per World-Space Canvas.
  • 95. 10 Canvases + X tagged objects Unassigned Assigned 10 0.07 0.06 100 0.13 0.07 1000 0.85 0.07 {X Timings are in milliseconds
  • 96. What can you do about this? • Avoid use of Camera.main • Cache references to Cameras. • Create a system to track the main camera. • Matters mostly in per-frame code
  • 97. What about in Unity UI? • “World Space” canvas? • Always assign a World Camera • Minimize number of Graphic Raycasters • Do not add them to non-interactive UIs!
  • 99. Setting dirty flags should be cheap, right? • Layout works on a “dirty flag” system. • When an element changes in a way that would invalidate the surrounding layout, it must notify the layout system. • Layout system = Layout Groups
  • 100. Wait but… • Layout Groups are components too! • Vertical Layout Group, Grid Layout Group… • Scroll Rect • They are always Components on parent GameObjects of Layout Elements.
  • 101. How do Layout Elements know which Layout Group to dirty?
  • 102. Walking the code… • https://bitbucket.org/Unity-Technologies/ui/ • Graphic.cs :: SetLayoutDirty • Base class for all drawable UI elements • UI Text, UI Image, etc.
  • 103. public virtual void SetLayoutDirty() { if (!IsActive()) return; LayoutRebuilder.MarkLayoutForRebuild(rectTransform); if (m_OnDirtyLayoutCallback != null) m_OnDirtyLayoutCallback(); }
  • 104. public static void MarkLayoutForRebuild(RectTransform rect) { // ... var comps = ListPool<Component>.Get(); RectTransform layoutRoot = rect; while (true) { var parent = layoutRoot.parent as RectTransform; if (!ValidLayoutGroup(parent, comps)) break; layoutRoot = parent; } // ... }
  • 105. Immediately, we see something… • MarkLayoutForRebuild searches for the Layout Group closest to the root of the hierarchy. • Stops if it doesn’t find a Layout Group. • Number of checks rises linearly with number of consecutively nested layout groups.
  • 107. private static bool ValidLayoutGroup (RectTransform parent, List<Component> comps) { if (parent == null) return false; parent.GetComponents(typeof(ILayoutGroup), comps); StripDisabledBehavioursFromList(comps); var validCount = comps.Count > 0; return validCount; }
  • 109. static void StripDisabledBehavioursFromList (List<Component> components) { components.RemoveAll(e => e is Behaviour && !((Behaviour)e).isActiveAndEnabled); }
  • 110. LINQ + List Modification in the hot path…
  • 111. Takeaways • Every UI element that tries to dirty its layout will perform at least 1 GetComponents call. • Each time it marks its layout dirty. • Each layout group multiplies this cost. • Nested layout groups are particularly bad.
  • 112. Things that mark layouts dirty… • OnEnable & OnDisable • Reparenting • Twice! Once for old parent, once for new parent • OnDidApplyAnimationProperties • OnRectTransformDimensionsChanged
  • 113. OnRectTransformDimensionsChanged? • Change Canvas Scaling Factor, Camera • Sent to all elements on Canvas • Change Transform anchors, position • Sent to all child RectTransforms • All the way to the bottom of the hierarchy!
  • 114. What else? • Images • Changing sprite or overrideSprite • Text • Changing most properties • Particularly: text, alignment, fontSize
  • 116. Enough. Let’s have some solutions. • Avoid Layout Groups wherever possible. • Use anchors for proportional layouts. • On hot UIs with dynamic number of elements, consider writing own code to calculate layouts. • Update this on-demand instead of with every single change.
  • 117. Pooling UI objects? • Maximize number of dirty calls that will no-op. • Disable first, then reparent into pool. • Removing from pool? • Reparent first, then update data, then enable.
  • 118. Hiding a canvas? • Consider disabling the Canvas component. • Stops drawing the Canvas • Does not trigger expensive OnDisable/ OnEnable callbacks on UI hierarchy. • Be careful to disable child components that run expensive per-frame code!
  • 119. In extreme cases… • Build your own UnityEngine.UI.dll from source. • Eliminate LayoutRebuilder. • This is a lot of work.
  • 122. Beware of Animators on UI! • Animators will dirty their elements every frame. • Even if value in animation doesn’t change! • Animators have no no-op checks. • Animators are OK on things that always change • … or use your own code or tweening system