There are as many different ways to optimize code as there are reasons for performance problems. In general, it is strongly recommended that developers closely profile their applications before attempting to apply CPU optimizations. However, there are several simple CPU optimizations that are universally applicable.
Unity does not use string names to address Animator, Material and Shader properties internally. For speed, all property names are hashed into property IDs, and it is these IDs that are actually used to address the properties.
Therefore, whenever using a Set or Get method on an Animator, Material or Shader, use the integer-valued method instead of the string-valued methods. The string methods simply perform string hashing and then forward the hashed ID to the integer-valued methods.
The property IDs created from string hashes are deterministic over the course of a single run. The simplest way to use them is to declare a static read-only integer variable for each property name, and use the integer variable in place of the string. These are automatically initialized during startup with no further initialization code needed.
The appropriate APIs are Animator.StringToHash for Animator property names, and Shader.PropertyToID for Material & Shader property names.
In Unity 5.3 and onwards, non-allocating versions of all Physics query APIs have been introduced. Replace RaycastAll calls with RaycastNonAlloc, SphereCastAll calls with SphereCastNonAlloc, and so on. For 2D applications, there are also non-allocating versions of all Physics2D query APIs.
The Mono and IL2CPP runtimes treat instances of classes that derive from UnityEngine.Object in a specific way. Invoking methods on the instances actually calls into engine code, which must perform lookups and validations to convert the script references to the native references. While small, the cost of comparing a variable of this type to null is much more expensive than a comparison against a purely C# class. For this reason, avoid these null comparisons in tight loops or in code that runs every frame.
For vector and quaternion math that is located in tight loops, remember that integer math is faster than floating-point math, and floating-point math is faster than vector, matrix or quaternion math.
Therefore, whenever commutative or associative arithmetic allows, attempt to minimize the cost of individual mathematical operations:
Vector3 x;
int a, b;
// Less efficient: results in two vector multiplications
Vector3 slow = a * x * b;
// More efficient: one integer mult, one vector mult
Vector3 fast = a * b * x;
It is common for applications that must convert between HTML-formatted color strings (#RRGGBBAA
) and Unity’s native Color
and Color32
structures to use a script from the Unify Community. This script was both slow and caused extensive memory allocation due to string manipulation.
As of Unity 5, there is a built-in ColorUtility API that performs these conversions efficiently. Usage of the built-in API should be preferred.
It is a general best practice to eliminate all usage of Object.Find
and Object.FindObjectOfType
in production code. As these APIs require Unity to iterate over all GameObjects and Components in memory, they rapidly become non-performant as the scope of a project grows.
An exception to the above rule can be made in accessors for singleton objects. A global manager object often exposes an “instance” property, and often has a FindObjectOfType
call in the getter to detect pre-existing instances of the singleton:
class SomeSingleton {
private SomeSingleton _instance;
public SomeSingleton Instance {
get {
if(_instance == null) {
_instance =
FindObjectOfType<SomeSingleton>();
}
if(_instance == null) {
_instance = CreateSomeSingleton();
}
return _instance;
}
}
}
While this pattern is generally acceptable, it is important to examine the code and ensure that the accessor is be called in Scenes where the singleton object does not exist. If the getter does not automatically create an instance of a missing singleton, it is very common to discover that code looking for the singleton results in repeated calls to FindObjectOfType
(often multiple times per frame) and creates an undesirable drain on performance.
The UnityEngine.Debug
logging APIs are not stripped from non-development builds, and do write to log files if called. As most developers do not intend to write debug information in non-development builds, it is recommended to wrap development-only logging calls in custom methods, like so:
public static class Logger {
[Conditional("ENABLE_LOGS")]
public static void Debug(string logMsg) {
UnityEngine.Debug.Log(logMsg);
}
}
By decorating these methods with the [Conditional] attribute, the define or defines used by the Conditional attribute determine whether the decorated method is included in the compiled source.
If none of the defines passed to the Conditional attribute are defined, then the decorated method and all calls to the decorated method are compiled out. The effect is identical to what would happen if the method and all calls to the method were wrapped in #if … #endif
preprocessor blocks.
For more information on the Conditional
attribute, see the MSDN website: msdn.microsoft.com.