Saturday, November 14, 2015

Why You should avoid the Update method!

TL;DR link

While working on SodaCity at first there were only a couple of dozen of objects in the scene but as the complexity grew, more and more objects were at the scene.
At the end of the project even a few thousands of objects were active, each one doing its things, enemies, bullets, trace effects, smoke particles, background animations, environment SFX, you name it.

At some point I started to see a decay in performance and I couldn't get to the source of the problem because the Unity Profiler was accounting a lot of the CPU usage to the 'Other' category.

Usually when you start your way into Unity3D, books, blogs, tutorials you are told how to use the main methods Unity uses Awake, Start, Update, LateUpdate and OnDestroy. But not really often they mention the overhead this causes.
Because MonoBehavior does not provide abstract or virtual methods for you to override, the only way it knows if a given behavior needs a particular method to be invoked it needs to use Reflection to figure it out. Besides I believe this causes overhead as it needs to get a hold of the reflected method and call invoke over the given object, etc.

So I decided to put my theory to the test.

You can download the test project from here.

To see details on the tests performed, continue reading...

Wednesday, November 11, 2015

Optimize Memory Usage and Performance when using Coroutines in Unity3d

Introduction

Hi!

I am Alejandro Santiago, co-founder of Tochas Studios. Main, and only programmer, in the team.
It took us three years to make SodaCity, our latest and biggest project so far, during that time we missed a few things, broke some others, overall learned a few tricks here and there. In this blog I will try to elaborate on those findings so anyone that stumbles upon this posts can learn a few things from us too and avoid the downsides.

As I am the 'tech' guy in the team most of my posts will cover technical topics.

In this first post I will address a small trick I came to find regarding the use of coroutines in Unity3d.

Optimize Memory Usage and Performance when using Coroutines in Unity3d


This topic was discussed at Unity forums in the following thread C# Coroutine WaitForSeconds Garbage Collection tip

The Coroutines in Unity offer great advantages for building complex behaviors and it use can span from a couple of objects to thousands as they can be used for AI agents, fade effects, motion controllers, bullets, particles, UI components, etc.

Guides on how to use coroutines and even nested coroutines are common, easy to find and digest.


But in all references I came across the same principle was applied.

IEnumerator MyCoroutine (Transform target)
    {
        while(Vector3.Distance(transform.position, target.position) > 0.05f)
        {
            transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);
            
            yield return null;
        }
        
        print("Reached the target.");
        
        yield return new WaitForSeconds(3f);
        
        print("MyCoroutine is now finished.");
    }

Yielding a new YieldInstruction every time a delay or pause in the coroutine execution is needed.

As the coroutine can be executed every frame or multiple times per second and the behavior can be attached to multiple objects (ex. Bullets or Enemies) this can potentially cause several even thousand of YieldInstructions to be created each frame.

This poses a problem with the GC, as we all know the GC can cause jitters and heavy frame drops each time it does its thing.

To avoid the GC problem it is recommended to pool objects. with almost every optimization guide I came across they referred to it as GameObject pooling. But I never came across a YieldInstruction pooling.

As SodaCity code grew in size and complexity each build was using more and more coroutines within more and more behaviors and objects. That had me worried as this could cause problems in the long run, So I decided to try to cache YieldInstruction objects and see what happened.

The results are as follows


Yes! You can and should pool or cache your YieldInstruction objects.


As all of the yielders provided by Unity do not expose members to allow changing values of already created objects, simply caching the references and reusing them at the instance level wouldn't do the trick (for most cases).

WaitForSeconds shortWait = new WaitForSeconds(0.1f);
WaitForSeconds longWait = new WaitForSeconds(5.0f);
IEnumerator myEvenAwesomerCoroutine()
{
    while (true)
    {
        if (iNeedToDoStuffFast)
        {
            doAwesomeStuffReallyFast();
            yield return shortWait;
        }
        else{
            dontDoMuch();
            yield return longWait;
        }
    }
}

The solution I am proposing is to use a generic Dictionary within a Yielders static class to allow cache and reuse of yielder objects at the application/game level.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public static class Yielders {
 
    static Dictionary<float, WaitForSeconds> _timeInterval = new Dictionary<float, WaitForSeconds>(100);
 
    static WaitForEndOfFrame _endOfFrame = new WaitForEndOfFrame();
    public static WaitForEndOfFrame EndOfFrame {
        get{ return _endOfFrame;}
    }
 
    static WaitForFixedUpdate _fixedUpdate = new WaitForFixedUpdate();
    public static WaitForFixedUpdate FixedUpdate{
        get{ return _fixedUpdate; }
    }
 
    public static WaitForSeconds Get(float seconds){
        if(!_timeInterval.ContainsKey(seconds))
            _timeInterval.Add(seconds, new WaitForSeconds(seconds));
        return _timeInterval[seconds];
    }
   
}

This can be done because it seems Coroutine objects only use YieldInstructions as an exit condition or objective, and each Coroutine object handle its current state independently of the current YieldInstruction yielding the execution or the GameObject the behavior is attached to.

This allows to globally pre-cache a single WaitForEndOfFrame and a single WaitForFixedUpdate object ready to be used by any coroutine in any object, even simultaneously.

In the case of WaitForSeconds we need to use the Dictionary using the float 'waitForSeconds' value as a key. This will allow to reuse a WaitForSeconds for a specific time interval.

To validate this theory and measure the real gains or losses for each method I made a small test project. It can be downloaded from here.

To see details on the tests performed, continue reading...