Unity WEBGL Optimization Techniques Part#3: Code OptimizationPosted by on

blog-post-featured-image-unity-optimization-part-3

Welcome to Part #3 in the series.  In this article we will be focusing strictly on various Code Optimization approaches and techniques.  Your results may vary as with all optimizations it really comes down to your project and the platform but knowing about these can greatly improve certain situations.

Since this section ended up being quite large, we thought it would be best to have a dedicated section in our series devoted to only the code optimizing of a project.


Code Optimization

Arguably, the best thing thing about OOP is everything is an object, that being said it is also  “Every time an object is created, memory is allocated. Very often in code, you are creating objects without even knowing it.” — Unity

Handling Strings and avoiding unnecessary garbage collection

Quoted from the Unity Docs:Handling strings and text is a common source of performance problems in Unity projects. In C#, all strings are immutable. Any manipulation of a string results in the allocation of a full new string. This is relatively expensive, and repeated string concatenations can develop into performance problems when performed on large strings, on large datasets, or in tight loops.”

For example, It’s common to have thing like this:

Debug.Log("New Value: " + value); // creates a new object, however this now leaves 2 objects for garbage collection.

string PlayerName = Firstname + "" + lastname); // creates a new object, leaves 2 objects for garbage collection.

As you can see from the examples above, when dealing with strings it doesn’t take long before you have accumulated a lot of garbage to be collected.

Note: The only way to optimize “Garbage Collection” is to avoid it in the first place.

Thankfully, C# offers us three ways to deal with this efficiently:

  1. StringBuilder
    StringBuilder MyString = New StringBuilder("Firstname ";;
    MyString.Append("Lastname);
  2. String.Concat
    String.Concat("Firstname ","Lastname");
  3. String.Format
    String.Format("{0} {1}", firstname, lastname);

All of these methods work however it can be argued which is the cut off of efficiency between them.  What is commonly agreed upon however is between 4 and 8 strings for the #2 Method of String.Concat will be fine. Should use start accumulating any more or when your unsure if you will go over that limit, #1 Method of StringBuilder is the way to go.  We actually avoided String.Concat altogether.

When you start planning out your project and you see that your project could end up having or needing 100’s of String.Concat, StringBuilder is definatly the way to go.   Since we decided to use StringBuilder and String.Format exclusively, we felt this shaved as much as we can from memory usage and Garbage Collection.

When you need to make a new string out of a variable and some text #3 Method does the trick. Using the String.Format allows you to create the string with the information without creating a new object.  This was used quite a bit for debugs.  But what about boxing you ask?

Note: Debugging is has a heavy overhead already, adding a + variable to display the value and doing so a number of times you can feel the difference.

An unfortunate byproduct of using string.Format is the fact it can cause unintentional boxing.  ( more on boxing further down in this article )

Example: playerHealth.text = string.Format("Current Health:{0}", val); // This boxes the variable val.

How to avoid boxing with string.Format?
Tell the compilier exactly what to do by adding .ToString() to the values.

Example: playerHealth.text = string.Format("Current Health: {0}", val.ToString());

If your interested in how efficiently StringBuilder VS String.Concat match up it terms of performance and overhead, check this article.


Monobehaviours on Everything

Something that new users to Unity tend to do is using Monobehaviours on everything.  This also falls back to the numerous tutorials who tend to show this so it becomes a learned approach without understanding when and if it should be used.

Certainly by having Monobevaiour’s on everything can be advantageous for debugging purposes, however when it is used excessively, it can waste a lot of CPU performance.  There is always a performance hit just by calling a MonoBehaviour function and it matters not what your code may or may not be doing.

This of course this has a lot to do with how MonoBehaviours scripts in Unity are created.  You can safely write scripts without inheriting from Monobehaviour when there is no need to place the script onto a GameObject.

ScriptableObjects are also an easy way to avoid using Monobehaviours and keep everything together.


Empty MonoBehaviour Methods

You may have noticed that by default, when you are creating new scripts within Unity they will include an empty Update() and  Start() functions.   We highly recommend you remove these if your not using them and they are empty.  Why? because even though they are not being used and the entire method is empty,  they still have a performance cost.  This is why it’s always best to just remove empty methods like this and add them later if you need to.

This is something you will find often with 3rd Party Assets.   We have seen assets from the Unity Asset Store that often had many scripts like this.

What are MonoBehaviour Methods?

MonoBehaviour  calls by default are pretty slow.  When we refer to these, the most commonly referred to and used in scripts inheriting from MonoBehaviour  are Update(), LateUpdate(), FixedUpdate(), and OnRender(). These are commonly referred to as the “magic methods” of MonoBehaviour.  For those of you that are familiar with object-oriented programming languages and concepts, this particular concept looks like were calling a method using the reflection mechanism (reflection enables method calls even if you don’t know the interface).

It’s important to know that these “Reflection calls” are really expensive performance wise.  As such, Unity will attempt to cache any and all operations.  Since they are caches, this will limit the set of CPU instructions that are needed to call these so called “magic methods” each frame. However, as great as this sounds, it can still be extremely slow.

How do we overcome this?

When your considering a way to bypass this, the first thing that should come to mind is a “Manager Script”.  The purpose of this manage will be to take the responsibility of calling Update() function rather than leaving it to be called by individual scripts.  By having a manger and defining your own function and calling it from a manager you are taking responsibility for updating your objects which gives you more control over your project and lead to a boost in performance. A manger solution will provide you the flexibility to “fire” monobehaviour methods in a responsible and efficient manner.

Consider that using a Manager will provide us with two key responsibilities:

  1. Keeping the list of managed objects updated.
  2. Calling update-like functions on managed objects when the manager script’s MonoBehaviour’s are called, for example: Update().

Do we need this?

Well we can’t see a reason why you would not want to do this.  Besides the fact that it can at first seem difficult to grasp however by asking yourself some quick questions can pinpoint whether or not you will see a boost and if it’s worth doing in your project.  Obviously a small project with a limited number of scripts with not see any real boost in performance.

Questions to Ask Yourself

  • Do you have many MonoBehaviour objects?
  • Do you have a lot of scripts that use the “Magic Methods” of monobehaviour?
  • Are you targeting mobile?
  • Do you have a lot of calls to certain calls or anything that is invoked each frame, for example Update() ?
    Note: Keep in mind it doesn’t necessarily need to be Update(), it can be anything that it is invoked with each frame.

Certainly there are more scenario’s than this that will result in a “Yes, we should use a manager”, however these are the most common encounters where this type of approach can help optimizing your code so that’s it is executed more efficiently.

How do we get started?

The good news here is someone has already gone through the trouble and created a fantastic base script to get you started.  We used this as a reference when creating our own manager, check it out here, it’s free and it ‘s clean, easy to follow code.

Before you get started it’s good to also understand the execution order of these behaviours which you can find all the information here.

For information on how Monobehaviours may impact your project, check out this article.


Avoid Searching For & Getting Components

When speaking about performance, it will always be recommended to set the actual reference to something within the UnityEditor.  One could go one step further by having a specific reference manager script that holds all the references to the objects in the scene rather then using such methods as GameObject.FindByTag() or GameObject.GetComponent() multiple times.  This also includes all the GetComponent()  convenience properties such as transform, light, rect etc.

Even though Unity will optimize these methods to operate as quickly as possible, they still will take a performance hit because they will still be required to search through all the relevant GameObjects just to find the one your looking for.  Consider if you have a group of 1000 people and they are all wearing name tags and your looking for one named “Bob”.  Now imagine how long it would take to check each person’s name tag to find “Bob”.  So much time is lost searching when you could have something that says “Bob” is 3rd row, 5th person in.

To avoid searching, rather then assigning a gameobject and then getting the different components, simply declare the actual value you need and drag and drop in the reference.

For example:

Rather than: GameObject myObject;  and then using: myObject.GetComponent<Transform>();

Transform myTransform;  => Then drag and drop the reference in the Unity Editor.   You can use this even for multiple references.

You could also access it by myObject.transform;

But what about if you instantiate the object and cannot set the reference in the editor?  This is where a manager script comes in.   By having a script that manages all the referenced you can have the instanced object tell the manager it’s alive and set it’s reference in the manager upon instantiating the object.

As you can see there are approaches that you can take to avoid the unnecessary performance loss and it’s highly and its recommended to avoid searching and getting components multiple times throughout your project.

Even though it can seem trivial when its hardly used, it’s still good coding practice to follow, but when you have a number of these, not doing it can can eat away at performance and they really offer no real benefit besides being there if you need them so you don’t have to type void Start(){}.

A more detailed article here, and a discussion on this here along with this article which shows some testing and the results.


Avoid Boxing

MSDN defines “Boxing” as “The process of converting a value type to the typeobject or to any interface type implemented by this value type.”

  • Boxing happens when you pass a value-typed variable such as an integer or float to a function with object parameters such as Object.Equals().
  • Boxing is something that should be generally avoided and is pretty much agreed upon in many tutorials and books.

For example, the function String.Format() takes a string and an object parameter. When we pass it a string and an int, the int must be boxed.

Therefore the following code contains an example of boxing:  string displayString = String.Format("Price: {0} gold", cost);

Boxing creates excess garbage because of what happens behind the scenes. When a value-typed variable is boxed, Unity will then create a temporary System.Object on the heap to wrap the value-typed variable.  A System.Object is a reference-typed variable, so when this temporary object is disposed of this creates garbage to be collected.

Boxing is an extremely common cause of unnecessary heap allocations. Even if you avoid boxing variables directly in your code, you may be using plugins that cause boxing or it may be happening behind the scenes of other functions.

It’s best practice to avoid boxing wherever possible and to remove or modify any function calls that lead to boxing.

This is a huge sore spot when dealing with 3rd Party Assets.   You can find this common place in 3rdParty assets.

Again this lies to the quality of tutorials which normally always show and present boxed variables as the approach to use when reality its really one of the dirty ways to code.


About Arrays & Lists

Arrays
Arrays are a good choice when you know the about of elements your going to have is fixed that will not change and you require to access those elements in a non-sequential fashion.

Linked-Lists
Linked-Lists are optimized for quick additions and removals at of elements at either end of the list however they are slow to access in the middle.

Array Lists
Array lists are actual a mixture of the two.  It allows for fast additions and randomly accessing elements.  They are the most commonly used.

When it comes down to it, you will want to take advantage of the performance gain Arrays offer when you know exactly the amount of elements that will be in the collection, otherwise your best alternative is an Array List.

Summary of Best practices concerning collections:

  • Avoid using foreach to iterate over a List. It is a lot slower than for(int i = 0; ).
  • foreach()and for()are almost the same cost of performance for Arrays.
  • Whenever you have a collection of things with a specific size or a size that does not change often for example, something like a players inventory capacity or how many players there are currently playing it’s always use Arrays instead of Lists since arrays are always more-performant.
  • Unity has a handy ArrayUtility class for doingContains()orFind()operations on Arrays.

Culling Code

Unity contains a feature that will check whether objects are within the frustum of a camera. If they are not within the frustum of a camera, code related to rendering these objects does not run. We had covered this in a previous article, the term for this is “frustum culling”.

As is with culling objects that are not in view, we can take a similar approach to the actual code we have in our scripts.  For instance, consider that we have some code that relates to the visual state of an object.  If the object is not visible to the player we likely do not need to execute this code.

When you have a complex Scene with many objects, for example: a bunch of mineable rocks or harvesting herbs or wood from trees, these object will have scripts to handle that however if they are not in the view/range of the player we can safely cull these which can result in considerable performance savings.

To further iterate this point, consider that we have a patrolling AI.  The script that controls the behavior of the AI calls two functions one every Update()  call.  These two functions are control moving the enemy, and the other related to its visual state.  If the player is on the other side of the map and cannot see or interact with this AI then we can disable the code until the player comes in range.   This will stop the AI from patrolling and looking for the player every Update which eats away at performance.   For this example there is clearly no reason to have this code run when its apparent it will not interact with the player at all.

How Do we Start?

When it comes to disabling code on objects that are not seen by the player, there is a few ways to approach this.   The most obvious and easiest way is when we clearly know that certain objects in our scene will not be visible or interactable with the player at a given point in the game, you can then manually disable those objects.

What if we don’t know?

We would then need to do some sort of calculation on the visibility of the object in relation to the player.  We could for example, check if the particular object is behind the player by using Unity functions such as OnBecameInvisible()andOnBecameVisible(), or we could do as far as doing a raycast. Another approach is to switch on and off objects is by using gameObject.SetActiveRecursively(false) and having a trigger that will also  disable scripts attached to the object when the player is out of range.

The best implementation and approach depends on your project.  Generally it will also require experimentation and profiling to obtain the best result.


Camera.main

Camera.main is a convenient Unity API call that returns a reference to the first enabled Camera component that has the tag “Main Camera”. This is a perfect example of something that may look like a variable, however it is in fact an accessor.

When you use Camera.Main, the accessor will call an internal function that is essentially a Find() call behind the scenes and it will suffer the same cost of performance that is similar you using the Find() call.   This is due to the fact it Unity will still search through all GameObjects and Components in memory to actually find the Camera.Main which results in a very expensive usage cost.

An easy way to avoid this potentially expensive call is either cache the result of Camera.main or avoid its use altogether and manually manage references to your project cameras.


Object Pooling

Object pooling is a technique where, instead of creating and destroying instances of an object, the objects are temporarily disabled for recycling and then reactivated when needed. This approaching is great for managing memory usage and reducing excessive CPU usage and garbage. Objects that are created during the projects runtime tend to have a short lifespan and when they are to be destroyed, the CPU will drain performance considerably the more you have over time.  Unity will use Garbage Collection in order recover previously used memory that’s no longer needed.  When you have a lot of objects like bullets that have repeated calls to Destroy()this can result in a slow down of the CPU can cause spikes that stutter or pause your project.

When it comes to “Object Pooling” it is quite often something that’s avoided due to the illusion that it’s complex or negligible in performance.  While this may be argued to be true for smaller projects, a larger one might benefit from such an approach.   You may have worked on projects past that create and destroy objects at will.

If not, let us consider a First-Person Shooter.  This type of project will no doubt have a lot of objects such as bullets and/or enemies. It’s typical for these and most assets you find along with the many tutorials will always prefer the approach to instantiate objects and then destroy them.  This will work and do the job, however now add to the this by considering you have a bunch of players shooting guns.  What if these are rapid fire, this could mean hundreds of objects being instantiated and destroyed.  Now imagine all that garbage that is being created and all the extra memory being uses along with CPU usage.  It’s easy to see how this can quickly get out of control and bottleneck and stutter your project.

Note: Object Pooling mainly addresses the problems with creating and destroying objects that are expensive and provide a way to avoid garbage by having a reference of an object already created, just disabled.

This can end up being something that you may not encounter during your development but when you hit beta testing and have multiple players now creating and destroying objects it can be gotcha.  You will experience this performance hit when you have a lot more going on you will come to know that the act of instantiating and destroying objects is very inefficient and can hit you with a big performance cost.  It can even go as far as to cause your project to slow down if not stutter during excessive garbage collection.   Certainly if your just doing this with a small number of objects won’t hurt your performance but when you have 100’s of these happening it really starts to bog down your project.

As we stated earlier, the only way to optimize garbage collection is to prevent garbage from being created in the first place.   This is where using object pooling for these types of objects is a lot faster than it would be creating and destroying them.  Object Pooling makes memory allocation simpler and removes the need for dynamic memory allocation overhead and creating a bunch of garbage for collection.

Do we need Object Pooling in our Project?

While it can be said that  it is significantly a higher performance cost to instantiate and destroy objects than it would be to pool them and simply deactivate and reactivate as needed. This is especially true when the object contains start up code, such as GetComponent() calls that are in an Awake() or Start() function.

If  you decide that your project requires the need to instantiate and destroy the same objects over and over but its just a couple objects it may cause more grief by using pooling.  Object Pooling is not without it’s quirks.   As we pointed out earlier, a good example for when object pooling would provide a performance increase is when your have bullets for a shooting project.  This is exactly type of situation where using this a pooling approach can provide some benefit. from

Key Points about Object Pooling?

  • Object Pools will provide the best performance when your using a fixed size principle with multiple objects that are instantiated and destroyed.
    Note: If that fixed size greatly exceeds the number of concurrently alive instances, you will be wasting memory.
  • If you find your unable to reasonably bound the maximum number of instances if will leave you with something that can grow by allocating another fixed-size block which then proceeds to undermine the benefits using an Object Pool.
  • If actual performance cost for resetting an object in the pool is higher than the performance cost of the objects initial construction.

Problems With Object Pools

It’s extremely common when people ask questions about how to avoid excessive garbage or gaining more performance from instanciating and destroying a huge number of objects the common answer is “Use an Object Pool” which is really not as much of an answer as it is a suggestion.   To have the idea that using an ObjectPool will “immediately and totally solve the problem” can lead you to a rabbit hole of unending .

While pools will often keep the garbage collector at bay, they’ll also introduce a whole slew of new problems that you’ve got to deal with instead. Today’s article goes through several of these problems so you’ll be aware of the tradeoffs involved and hopefully avoid some pitfalls.

Reference Leaks: object is registered somewhere within your system, although did not get returned to the pool. This happens quite often, and leads to out of memory errors that are hard to track down. It gets particularly hard when you have references leaking under just one subtle scenario.

Some of the more common problems with ObjectPooling:

  • Premature Object Recycling: How? This can happen as the result of code that decides to return the object to the pool when the object still has a reference to it.
  • Implicit Object Recycling:  How? This can happen when working with reference counts and due to concurrent access, or error in one of the consumers the object may be implicitly recycled even while your code is expecting that reference should still be valid.
  • Sizing Error: How? This problem commonly arises when using arrays.  Consider that you specify 20 bullets for the object Pool array however due to circumstances all the bullets are used and you no longer have any available you can encounter an IndexOutOfBounds error or similar, which arises whenever your trying to read/write from/to a location that is outside of range of the generated object.   This can be avoiding by automatically resizing as needed.
  • Resizing: How? When having a pool that will resize itself as needed but you also do not allow it downsize you can be left with a large amount of objects sitting in the object pool unused and taking up memory.   If you never downsize after upsizing you can lost a lot of memory due to the oversized object pool.
  • Double Booking: How? This can happen when two objects are set to recycle the object they received after use however the reference to only one of them was actually known by the object itself. This is referred to as a “reference leak”. It is important to know this when your object is being used by multiple methods or functions that have varying degrees of performance and one of them returns the object to the pool before the other which it is returned but then reused and the remaining reference is now  garbage.

For more on the problems that you can encounter with object pooling there is already a great article here.

A full  guide to object pooling is beyond the scope of this article, but it’s a really useful technique and one worth learning. This tutorial on object pooling on the Unity Learn site is a great guide to implementing an object pooling system in Unity.  We however recommend checking out this article on creating an Object Pooler as we found it really goes into the extra detail and the approach is quite a refreshing view point.


Coroutines

Coroutines have a tiny overhead and are actually cheap on the performance cost when they are used in numbers.   For example, having 20-30 AI moving around with Coroutines will not cause a bottle neck on most hardware however having hundreds of these can impact performance, even if they are empty.

Another thing that can rear it’s head when using coroutines is the Garbage Collector.  Everytime you use StartCoroutine,  9 bytes of memory will be allocated and on top of that, every time you use yield return new WaitForSeconds(1f) you’re are actually generating even more garbage.

Conclusion

That being said it’s easy to see how a small number of them can provide benefits for calls that have a tiny overhead should be used instead of using an Update method that is called all the time needlessly. Consider that if you have a script that will open and close a door on command you can use a coroutine instead of in Update to handle that.  Doing things like this will ensure the script has a minimal overhead cost to performance as opposed to using the Update function.


Events

As your probally aware, Unity game objects have methods that are similar to events in which they can be used to let other objects know that something has happened when something has triggered it.  This is achieved by using the SendMessage, SendMessageUpwards and BroadcastMessage methods.  These methods send an event up or down the game object hierarchy.

When you find yourself sending to communicate different things and values you will often seeSendMessage()being used.  This is extremely true for the majority of assets you find on the UAS.  One argument on why these are so over used is they are simple and easy to use which is why more people tend to use these when starting out.  Events seem to scare people and beginners tend to find them hard to understand let a lot work with.  Another issue with this approach is the event is a string and there is no guarantee you did not include a typo.

On the contrary, events are rather simple to use and when talking about performance, they cost a lot less in the performance department then using SendMessage().

There are two types of events available for usage within Unity, the first one being “C# Events” and the second being  “Unity Events”.

C# Events

These are essentially just standard C# delegate functions with extra restrictions which are designed to be populated by code at run-time.  These events are subscribed to by other classed by using theOnEnable()andOnDisable()functions.   C# events can only be invoked from the specific class that actually declared the event and other classes can only add or remove a listener.  In this sense, C# events are protected from public access and you cannot see the subscribers of the event.

Unity Events

Unity Events” are a way to hook up function calls between GameObjects in the editor and serialize those calls. They are designed to be populated during development at design-time.  Unity events can do what C# events do by populating  with function calls at run-time via UnityEvent.AddListener(). While it is similar it can also so a lot more but at the sake of things being available more publically.  Unity allows other classes to effectively disable all the listeners on a “UnityEvent” via the UnityEvent.SetPersistentListenerState().  Another advantage of “UnityEvents” is the fact it comes with a visual graphical interface that lists all the listener of the UnityEvent along with the ability to add Listeners right in the interface.

Note: When dispatched, C# events create no garbage when dispatched VS 136 bytes of garbage that “UnityEvents” will create.  It is important to note that  “UnityEvents” will only create garbage from the original dispatch.  However “UnityEvents” will create less garbage then C# events when you have more then two listeners to it, otherwise it will create more.

Conclusion

To sum up the differences, “C# Events” allow scripts/code  to subscribe to these events where as “Unity Events” allow public interfaces to be connected within the UnityEditor. “UnityEvents” take anywhere from 2x to 40x longer to process then C# Events.

To see the actual Benchmark performance.  Need to know more about using Unity Events? Check out this Video Tutorial.


Clean Code

When you first start out in Unity Development, it’s relatively easy to find tons of tutorials and scripts all over the internet.  It should be said that although some of the approaches they show are great to start with and easy to follow but can lead to issues later on the more involved your code and project become.   The easy way is not always the best way.   The problem with this is the bad habits that can be learned and then reproduced during development of real projects.

Use Meaningful Names

When it comes to naming variables, methods, scripts or classes we should use names that clearly represent what the purpose is, why it exists and how to use it.  A general rule of thumb here is “If you need to add a comment to explain the variable, you should choose a better name.”

Functions VS Spaghetti Code

Functions should be small and perform a specific purpose.  When your first learning or even using 3rd Party Assets you tend to see everything packed into the Update() method rather then being split up into different functions for each purpose.  Having everything jammed together in one method, avoiding polymorphism are some examples of dirty code which is generally referred to as Spaghetti Code.

General rule of thumb regarding Functions: “Functions should do one thing. They should do it well. They should do it only.”

Spaghetti Code appears as code that contains very little , if at all, structure.   More on Spaghetti Code and what constitutes being classified as such and the downfalls of it here.

Good comment VS Bad comment

How to approach the use of comments is often referred to as “a double-edged sword”.

What can be wrong with comments?

While it can be said nothing is more helpful then a proper-placed comment, it is also agreed upon that nothing can be more damaging then a deprecated comment that provides the wrong information and details about the code.  Using redundant comments is also one of the worst misuses of comments.

For example: int playerHealth = 100;  // This is the current player heath.    <– This is a bad comment.

Would be better written as: int currentPlayerHealth = 100;    <– No comments required as it is clear what variable is.

Programmers will disagree about what the accepted general role of comments should be .  It’s commonly debated as whether they are good and purposeful VS a necessary evil in programing. It can be said that as a matter of fact, programmers often will use comments to describe bad code that is hard to read or using some absurd naming convention.  If you find yourself requiring to write a bunch of comments about your code then that is a clear sign you should clean it up. After all, having clear and meaningful comments on bad code will still result in having “Bad Code”.

Embracing OOP

In OOP (“Object Orientated Programming“), encapsulation and abstraction are some of the core benefits for OOP.

Abstraction : allows representation of complex real world items in simplest way. It is a process of identifying the essential features and behaviours that an object should possess.  Abstraction is essentially focusing on all necessary details about the object.

Encapsulation:  is a process of hiding all the internal details of an object from the outside real world.  Encapsulation is essentially hiding the details of the object and providing a decent interface for the entities in outer world to interact with that object.

When your just starting out,  it’s recommend not to worry a whole lot about writing clean code but its important to be away of clean coding techniques.  It’s not expected that you will write clean and efficient code while learning however in production it’s expected.  Having clear idea and foundation of what clean and proper code is you can start to bring that into your learning process.


Summary of Code Optimization:

  • Remove all the empty Monobehaviour methods (ie: OnGUI, LateUpdate, Update, etc) from all scripts
  • Use the Unity Canvas instead of Unity GUI as the latter comes with a huge performance cost.
  • Consider using a HashSet instead of Lists if you need to use “Find” or “Contains” since it is a data structure designed for quick searches.
  • Cache references and use the UnityEditor to assign rather than using the high cost of search and find.
  • Consider using a GameManager script that will link all of your objects together rather then using “Find”.
  • Consider using Arrays, which are much faster instead of using a list for a collection that never changes size.
  • Consider caching your components on Awake() or Start() such as transform, rigidbody, etc  to use them later.
  • Remember that if you use AwakeFromLoad, this has a very expensive performance and stuttering of the main thread.
  • Consider using object pooling whenever it makes sense as it is a lot faster and less of a performance cost than instantiate and destroying.
  • Remember if your using Linq at runtime, use it sparingly as it has some extremely slow performance.
  • Avoid using Reflection at runtime under any circumstances, the performance hit is not worth it.
  • Avoid using Unity’s SendMessage() system. It has a high performance cost.  Events are a better option here.
  • Avoid using foreach, lamda, and LINQ structures since these allocate memory needlessly at runtime.

This concludes our Code Optimization article.   This information may or may not apply to your current project however they are great points to know and be aware of during your coding endeavours.

If you would like even more detailed information on script optimization, check out this article here Unity Doc’s.

Image-Logo-AresTheDogStudio

ProgrammingUnity

C#PerformanceUnity

AresTheDogAuthor posts

AresTheDog Studio Administrator.

3 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *