Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Optimising with Unity for iOS

Discussion in 'iOS and tvOS' started by Toxic Blob, Feb 26, 2011.

  1. Toxic Blob

    Toxic Blob

    Joined:
    Jun 8, 2010
    Posts:
    18
    I originally posted this on my website at http://www.toxicblob.com, but thought people here might also find it useful. I also thought it’d be great to expand on this with further ideas from the community. When I get a moment I’ll also post some code samples if people are interested



    I’ve found it’s often the unexpected that takes time. Cuban customs agents, downloading in Canada, and bureaucracy in Norway. And so it has been with optimising my game for the iPad. Unity provides two useful pages to get one started on optimising; one for optimising scripts and one for optimising graphics. There are still further, and often simple, code changes that help squeeze more frames per second out of the iPad.

    Use Strict
    Use #pragma strict at the top of all your scripts. It’ll initially make component access more awkward but as you’ll cache those lookups it’s a one time hassle. So with #pragma strict: GetComponent(Rigidbody) would become GetComponent(Rigidbody) as Rigidbody.

    Avoid Object.Instantiate() Object.Destroy()
    Instantiating is bad. Destroying is bad. Both require memory allocation changes and cause a hiccup in performance when an object is created or destroyed. So instead of creating and destroying objects when needed, I predetermine what is required and my SpawnManager class instantiates all required objects at the beginning of the game - causing one large hiccup when it can be hidden. Then it disables the spawn, but they’re kept waiting in memory. When they’re needed the game object is enabled and ready to go.

    Cache Component Lookups
    This is an optimisation recommended by Unity on their optimising scripts page, and I whole heartedly agree. I’ve found casual component lookups performed often enough cause a slow down.

    Use iTween Sparingly
    I hadn’t used iTween until midway through production, then after some positive encouragement I gave it a try. And it was awesome. Very easy to use and easy to chain together to create complex behaviours. I loved it, and I quickly incorporated it into my movement scripts. Then the performance hiccups followed.

    A call to iTween typically happens midway through a game. An iTween component is instantiated, makes some expensive component lookups and then is destroyed. Each of these steps causes a performance hiccup, the worst being a substantial garbage collection on destruction. Instead of using iTween in my performance critical areas I now use my own easing and interpolation classes that slip into existing Update functions and can be called with cached nodes.

    Avoid SetActiveRecursively()
    My SpawnManager class used to execute gameObject.SetActiveRecursively(true) on any node that was being spawned. The first disadvantage to this was sometimes I didn’t want all children to appear right away, so I’d hide them again. More performance offensive was that SetActiveRecursively performs several expensive component lookups.

    To solve this I now cache the hierarchy in Awake for any game object that will be spawned by SpawnManager. SpawnManager then simply enables the top most node and the top node is responsible for enabling whichever children it needs. And because the children are cached in that initial Awake call, there is little to no performance hit during the game.

    Use Builtin Arrays
    Unity isn’t kidding when they recommend using Builtin Arrays for critical code. In my SpawnManager I was tracking which objects were active and which were not using the handy ability of Javascript arrays to resize. SpawnManager.GetActive() would then convert and return those arrays as Builtin arrays for other scripts. But the return activeObjs.ToBuiltin(GameObject) was using 108B of memory and taking 1.3% of a frame’s time. Not ridiculous amounts, but more than I found acceptable. Converting my code to use Builin Arrays took four more lines of code and now uses less than a quarter of the memory and is 5x faster.

    Avoid String Comparison
    Initially I had plenty of conditionals using tags to query objects. I’ve collided with you, are you tagged with “The Fancy Cliff Over Yonder”? Great, lets form a club. Lets also slow down the game, because the longer the tags, the longer it takes to compare against. This may seem trivial, and for a few tag comparisons here and there not really a problem. But in an Update function with several objects this suddenly becomes several hundred queries a second. So if(collision.gameObject.tag=="Cliffs") became if(collision.gameObject.layer==9) which isn’t as easy to read, but a few explanatory comments nearby and the problem is solved. @Superpig suggests defining an enum with all the required layer names to improve readability.

    Avoid Vector3.magnitude Vector3.Distance()
    Every moment of my game I’m comparing the position of the finger to the position of the interactive characters. Several characters on screen at once and this starts to amount to an awful lot of Vector3.magnitude checks (Vector3.Distance uses .magnitude and is essentially the same). This becomes an awful lot of slow square roots calculations. So, wherever possible compare distances using Vector3.sqrMagnitude.


    Note
    My game is programmed in Javascript so those using C# may find subtle differences. Performing the above optimisations has helped increase my average frame rate from 30-60fps to 100-200fps.
     
  2. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    Are you sure you got any speed boost from switching to int comparison instead of string comparison? Comparing strings is expensive in some languages (such as C), but in .NET (and this Mono), strings are immutable. A string comparison should be the same as an integer comparison, because it's just comparing addresses.
     
  3. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,121
    Using that is necessary for iOS anyway (or at least, the code must be able to compile as if you were using it). It's not just a good idea, it's the law.... You can use generics to make it simpler: GetComponent.<Rigidbody>()

    If you need dynamically-sized arrays, use List. With complex types, List is nearly as fast as built-in arrays.

    Comparing strings is significantly slower (about 5X) than comparing integers.

    --Eric
     
  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,603
    or instead of lists, follow the "binary thinking": if you need to resize arrays grow and shrink them only by factors of 2, not +1 or +x with x != array.Length and terrible ideas like this.
    then they will normally be blazing fast too as the really costly thing isn't the ref copy but the garbage collection of the fast dumping and the too regular memory alloc

    if you really need per frame add and remove and cover a large scale on the length side so you have to expect many shrink and expands, then using List<> as Eric says is the best way to go
     
  5. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    I was speaking out of ignorance: I didn't realize that strings weren't always interned in Mono. That means that string comparison takes time proportional to the length of the common prefix of the two strings, not a constant like 5.
     
  6. Toxic Blob

    Toxic Blob

    Joined:
    Jun 8, 2010
    Posts:
    18
    Are both of those in JavaScript as well as C#? I’ll have to look into those as they both sound very appealing.


    Not sure if I was experiencing precisely a 5x difference in speed, but yup, the tag comparison was definitely showing up as a considerable speed hit in the profiler and changing to an int comparison has been faster.
     
  7. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,121
    Yes; I was using an average for a typical tag. There's not much difference unless you use particularly long strings, so it's mostly about 5X faster to use ints.

    Since you were talking about Javascript, I wouldn't have mentioned them otherwise, and "GetComponent.<Rigidbody>()" is JS syntax and wouldn't compile in C#. ;)

    --Eric
     
    twobob likes this.
  8. corey.stpierre

    corey.stpierre

    Joined:
    Nov 18, 2008
    Posts:
    79
    - jamiem

    I am in the process of implementing this optimization for my iOS game. I am curious as to how you go about caching the hierarchy. Do you use tags, then use FindGameObjectsWithTag()? Or is there another way? I know has to be done in Awake().

    - Eric5h5

    I'll assume it's useful to store these gameobjects (the hierarchy) in a List? This will be good for supporting dynamic gameobjects, i.e. gameobjects that get added to the hierarchy post Awake().

    Thanks everyone.
     
  9. Rafes

    Rafes

    Joined:
    Jun 2, 2011
    Posts:
    764
    Great post!

    We released PoolManager for just this reason. (Video at http://poolmanager.path-o-logical.com). The call to Destroy() alone was noticeable without any performance monitoring, so removing it was the highest priority.

    There are some things on your list worth considering (I like the idea of caching a hierarchy in Awake(). I'll have to run some tests soon), and we will be sure to roll them in to PoolManager as we find the need - or if we get a request from a user.)
     
    Last edited: Jul 20, 2011
  10. Toxic Blob

    Toxic Blob

    Joined:
    Jun 8, 2010
    Posts:
    18
    I cache the hierarchy (well, all the children sans the parent) using two functions - depending on how I’ll need to access them later.

    Either as a simple built-in array...
    Code (csharp):
    1. //  · returns all children  grandchildren of the specified gameObject
    2. //  · does NOT return the specified gameObject as GetComponentsInChilden does
    3. //  · as we use an array and convert it this is best used during an Awake and not in Update
    4. static function GetChildren(_obj:GameObject) : GameObject[]
    5.     {
    6.     var allChildren = new Array();
    7.     for(var child : Component in _obj.GetComponentsInChildren(Transform))
    8.         if(child!=_obj.transform)
    9.             allChildren.Add(child.gameObject);
    10.     return allChildren.ToBuiltin(GameObject) as GameObject[];
    11.     }
    Or as a dictionary so we can access them easily by name...
    Code (csharp):
    1. //  cache the hierarchy into a dictionary for easier control
    2. //
    3. //  usage:
    4. //      private var nodes : Dictionary.<String,GameObject> = new Dictionary.<String,GameObject>();
    5. //      nodes = Utilities.GetChildrenAsDictionary(this.gameObject);
    6. //
    7. static function GetChildrenAsDictionary(_obj:GameObject) : Dictionary.<String,GameObject>
    8.     {
    9.     var nodes : Dictionary.<String,GameObject> = new Dictionary.<String,GameObject>();
    10.     for(var cChild:GameObject in GetChildren(_obj))
    11.         nodes[cChild.name] = cChild;
    12.     return nodes;
    13.     }