Skip to content

Benchmark

yenmoc edited this page Jan 16, 2024 · 9 revisions

Hierarchy

https://blog.unity.com/engine-platform/best-practices-from-the-spotlight-team-optimizing-the-hierarchy

image

Heavy Composition

image

Optimized Composition

image

Managed Update

Traditional

public class Mover : MonoBehaviour
{
	float _speed;

	void Awake()
	{
		_speed = Random.Range(1.0f, 1.1f);
	}

	void Update()
	{
		moveUpAndDown();
	}

	void moveUpAndDown()
	{
		var currPos = transform.position;
		transform.position = new Vector3(currPos.x, Mathf.PingPong(Time.time * _speed, 10f), currPos.z);
	}
}

Managed

public class ManagedMover : MonoBehaviour
{
	float _speed;

	void Awake()
	{
		_speed = Random.Range(1.0f, 1.1f);
	}
	
	void OnEnable()
	{
		// Registers the script into the UpdateManager
		UpdateManager.Add(this);
	}

	public void UpdateManager_Update()
	{
		moveUpAndDown();
	}

	void moveUpAndDown()
	{
		var currPos = transform.position;
		transform.position = new Vector3(currPos.x, Mathf.PingPong(Time.time * _speed, 10f), currPos.z);
	}

	void OnDisable()
	{
		// Unregisters the script from the UpdateManager
		UpdateManager.Remove(this);
	}
}
public static class UpdateManager
{
    public static Stopwatch SW { get; private set; } = new Stopwatch();
    public static Action StopWatchStoppedCallback;

    static HashSet<ManagedMover> _updateables = new HashSet<ManagedMover>();

    /// <summary>
    /// Adds a <see cref="Mover"/> to the list of updateables
    /// </summary>
    /// <param name="obj">The object that will be added</param>
    public static void Add(ManagedMover mover)
    {
        _updateables.Add(mover);
    }

    /// <summary>
    /// Removes a <see cref="Mover"/> from the list of updateables
    /// </summary>
    /// <param name="obj">The object that will be removed</param>
    public static void Remove(ManagedMover mover)
    {
        _updateables.Remove(mover);
    }

    class UpdateManagerInnerMonoBehaviour : MonoBehaviour
    {
        void Update()
        {
            SW.Restart();
            foreach (var mover in _updateables)
            {
                mover.UpdateManager_Update();
            }
            SW.Stop();
            StopWatchStoppedCallback?.Invoke();
        }
    }

    #region Static constructor and field for inner MonoBehaviour

    static UpdateManager()
    {
        var gameObject = new GameObject();
        _innerMonoBehaviour = gameObject.AddComponent<UpdateManagerInnerMonoBehaviour>();
#if UNITY_EDITOR
        gameObject.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
        _innerMonoBehaviour.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
#endif
    }
    static UpdateManagerInnerMonoBehaviour _innerMonoBehaviour;

    #endregion
}

Spawner

public class Spawner : MonoBehaviour
{
    /// <summary>
    /// Use this flags to change updating logic from manual to Unity
    /// </summary>
    public static bool UseUpdateManager { get; set; } = false;

    /// <summary>
    /// The prefab type that this script spawns
    /// </summary>
    [SerializeField] GameObject _objectToSpawn;

    void Start() { SpawnObjects(100); }

    /// <summary>
    /// Will spawn count * count - 1 objects in the scene
    /// </summary>
    public void SpawnObjects(int count)
    {
        for (int i = 0; i < count * 2; i = i + 2)
        {
            for (int j = 0; j < count * 2; j = j + 2)
            {
                if (i == 2 && j == 2) continue;
                var go = Instantiate(_objectToSpawn, new Vector3(i, 0f, j), Quaternion.identity, transform);
                if (UseUpdateManager) go.AddComponent<ManagedMover>();
                else go.AddComponent<Mover>();
            }
        }
    }
}

Benchmark

public class UpdateBenchmark
{
    private const float WARMUP_TIME = 5f;
    private const float EXECUTION_TIME = 60f;

    [UnityTest, Performance]
    public IEnumerator UpdateManager_Benchmark()
    {
        Spawner.UseUpdateManager = true;
        SceneManager.LoadScene("Update Manager");

        var sampleGroup = new SampleGroup("Update Time", SampleUnit.Millisecond);

        // Wait a frame for scene load
        yield return null;

        // Small idling before measurement starts
        yield return new WaitForSecondsRealtime(WARMUP_TIME);

        UpdateManager.StopWatchStoppedCallback += () => Measure.Custom(sampleGroup, UpdateManager.SW.Elapsed.TotalMilliseconds);

        yield return new WaitForSecondsRealtime(EXECUTION_TIME);

        UpdateManager.StopWatchStoppedCallback = null;
    }

    [UnityTest, Performance]
    public IEnumerator TraditionalUpdate_Benchmark()
    {
        Spawner.UseUpdateManager = false;
        SceneManager.LoadScene("Update Manager");

        var sampleGroup = new SampleGroup("Update Time", SampleUnit.Millisecond);

        // Wait a frame for scene load
        yield return null;

        // Add the helper monos
        var go = new GameObject("Temp mono holder");
        go.AddComponent<UpdateBenchmarkHelperJustBefore>();
        go.AddComponent<UpdateBenchmarkHelperJustAfter>();

        // Small idling before measurement starts
        yield return new WaitForSecondsRealtime(WARMUP_TIME);

        UpdateBenchmarkHelperJustAfter.StopWatchStoppedCallback += () => Measure.Custom(sampleGroup, UpdateBenchmarkHelperJustAfter.SW.Elapsed.TotalMilliseconds);

        yield return new WaitForSecondsRealtime(EXECUTION_TIME);

        UpdateBenchmarkHelperJustAfter.StopWatchStoppedCallback = null;
    }
}

Result

image

Vector Multiplication

Directly Mul

Vector3[] input = new Vector3[1000];

... // init vector input to test

Vector3[] DefaultVectorMultiplication()
{
    Vector3[] results = new Vector3[1000];

    for (int i = 0; i < 1000; i++)
    {
        results[i] = input[i] * 5f;
    }

    return results;
}

Custom Mul

Vector3[] input = new Vector3[1000];

... // init vector input to test

Vector3[] DefaultVectorMultiplication()
{
    Vector3[] results = new Vector3[1000];

    for (int i = 0; i < 1000; i++)
    {
        Vector3 curr = input[i];
        Vector3 result;
        result.x = curr.x * 5f;
        result.y = curr.y * 5f;
        result.z = curr.z * 5f;
        results[i] = result;
    }

    return results;
}

Result

image

Clone this wiki locally