There are as many different ways to optimize code as there are reasons for performance problems. In general, it’s 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 doesn’t use string names to address Animator, Material, and Shader properties internally. For speed, all property names are hashed into property IDs, and it’s 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 perform string hashing and then forward the hashed ID to the integer-valued methods.
从字符串哈希创建的属性 ID 在单次运行过程中是不变的。它们最简单的用法是为每个属性名称声明一个静态只读整数变量,然后使用整数变量代替字符串。启动期间将自动进行初始化,无需其他初始化代码。
Animator.StringToHash 是用于 Animator 属性名称的对应 API,Shader.PropertyToID 是用于 Material 和 Shader 属性名称的对应 API。
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. 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. It’s more resource intensive to compare a variable of this type to null than to compare 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’s 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 resource intensity of individual mathematical operations:
Vector3 x;
int a, b;
// 效率较低:产生两次矢量乘法
Vector3 slow = a * x * b;
// 效率较高:一次整数乘法、一次矢量乘法
Vector3 fast = a * b * x;
It’s 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.
从 Unity 5 开始,有一个内置 ColorUtility API 可以有效执行此类转换。应优先使用内置 API。
It’s a general best practice to eliminate all usage of GameObject.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.
在单例对象的访问器对上述规则来说是个例外。全局管理器对象往往会暴露“instance”属性,并且通常在 getter 中存在 FindObjectOfType
调用以便检测单例预先存在的实例:
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’s important to examine the code and ensure that the accessor is be called in Scenes where the singleton object doesn’t exist. If the getter doesn’t automatically create an instance of a missing singleton, it’s 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 aren’t stripped from non-development builds, and do write to log files if called. As most developers don’t intend to write debug information in non-development builds, it’s 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);
}
}
通过使用 [Conditional] 属性来修饰这些方法,Conditional 属性所使用的一个或多个定义将决定被修饰的方法是否包含在已编译的源代码中。
如果传递给 Conditional 属性的任何定义均未被定义,则会被修饰的方法以及对被修饰方法的所有调用都会在编译中剔除。实际效果与包裹在 #if … #endif
预处理器代码块中的方法以及对该方法的所有调用的处理情况相同。
有关 Conditional
属性的更多信息,请参阅 MSDN 网站:msdn.microsoft.com。