Search Unity

iPhone optimization tips

Discussion in 'iOS and tvOS' started by nikko, Jun 16, 2009.

  1. nikko

    nikko

    Joined:
    Mar 20, 2009
    Posts:
    436
    I've found so many little tricks all over the place on this forum, and always the same question, my application is slow how do I optimize it?

    The iPhone remind me the old times of the Oric, C64, Atari and Amiga. Which is not a bad thing for a programmer...

    I start this thread in the hope that more experienced programmer will give here their optimization tips to help the newbies to get the most of Unity and some useful tips no one will teach at school but are used in games.

    In general, some good practices: have a global script attached to an Empty game object where you will put all your 'static' variables you use repetitively elsewhere in the game, for example

    In a file GLOB.js

    Code (csharp):
    1.  
    2. static timenow:float=0;
    3. static count=0;
    4. static globalrotate:float=0;
    5.  
    6. function FixedUpdate(){
    7.   timenow = Time.realtimeSinceStartup;
    8.   count+=1;if (count>10) count=0;
    9.   globalrotate=Mathf.Sin(Time.deltaTime);
    10. }
    11.  
    Now if you need to check the absolute time spent between 2 calls to an Update, use GLO.timenow.
    If you want to run a complex operation every 10 time an Update is called, check GLOB.count
    If you need to rotate a bunch of turrets then use globalrotate, you will save a lot of CPU cycles.

    Code (csharp):
    1. function Update(){
    2.  if (count==0) {
    3.    var touch:Vector2=iPhone.GeTouch(0).position;
    4.  }
    5. }
    is slow and should be

    Code (csharp):
    1. var touch:Vector2;
    2.  
    3. function Update(){
    4.  if (count==0) {   touch=iPhone.GeTouch(0).position;}
    5. }
    6.  
    Some task do not need to be called every Update, for example : the enemy aim, the player IA every and the animations logic will be called every x updates.

    enemyanim.js

    in the Update function :

    Code (csharp):
    1.  
    2. if (GLO.count%3){
    3.   // execute the enemy aim at your player every 3 Update
    4. }
    5. if (GLO.count%5){
    6.   // execute player IA  every 5 Update  
    7. }
    8.  
    Another little one, I don't know if Unity's compiler is doing this kind of optimization but it is always better to use multiplications instead of divisions.

    Code (csharp):
    1. x=x/2 is much slower than x=x*0.5
    I'll add more with time, feel free to contribute and share your tips here!
     
  2. ader

    ader

    Joined:
    Jan 27, 2009
    Posts:
    155
    Where possible ditch the OO principle of having behaviour scripts on your game objects. Instead have one central script in which you update your game action. The fewer the update functions the better. OOP is great if you want to share and re-use code - but is slower to write and run. In my experience. On low end devices procedural programming is often where it's at.

    Use mesh/game object combining where possible to save draw calls.

    Try to avoid loading/instantiating resources for the first time during gameplay - if possible instantiate them once before the game play starts, destroy them and thereafter they will be quicker to instantiate.

    Avoid lighting/lights.

    Code (csharp):
    1.  
    2. for (i=0; i<myarray.length; i++) {
    3. }
    4.  
    is slower than:

    Code (csharp):
    1.  
    2. arraylen = myarray. length;
    3. for (i=0; i< arraylen; i++) {
    4. }
    5.  
    Stinkbots XCode tweak is awesome:
    http://forum.unity3d.com/viewtopic.php?t=24987

    If you're not using the accelerometer at least lower it's frequency of update in XCode.

    Instead of checking the count var in GLOB.js use invoked functions e.g. to update an enemy count or NPC status every n seconds. See Invoke and InvokeRepeating.

    Compare performance using compressed or uncompressed images.

    Test, tweak, test, tweak etc.

    Disclaimer: I'm happy to be corrected on any of the above
    :D
     
  3. friken

    friken

    Joined:
    May 1, 2009
    Posts:
    84
    I have tried using this to time how long an update takes by doing this:

    mytime = globals.timenow;
    ....all my update
    Debug.Log("timeused=" + (mytime-timenow));

    I always get 0, if I debuglog the time.now at start and finish of the update the numbers are the same. It doesn't look like there is enough precision to the #s or the fixedupdate of globals isn't getting called during another object's update.

    I had to use Time.realtimeSinceStartup directly instead of the globals timenow to get a result
     
  4. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    This is great, but I think we need to be careful when counseling things as absolute truths.

    For example, the statement that local variables are evil, stems from the fact that stack allocation on the iPhone is expensive. On the other hand, however, chewing up a lot he heap memory persisting variables that logically should not be persisted is also not a good idea. And indeed, if you hit the memory limit allocated to your app on the phone, you are all done. So, for example telling those new to scripting to declare all variables outside of functions or methods is misleading and potentially harmful. For references to complex objects that would have to be looked up, or variables which are static or largely static, this, of course, makes complete sense. But the advice is context sensitive.

    Another example is the "put all update code into one huge switchyard Update method or function advice". Again, it depends. If you have to add a ton of logical checks across that switchyard so that only pertinent parts of the code are run at different times, this can be ***slower*** than running a few different controller scripts with their own Update functions. I recently went into the lab on this one, and I didn't see a significant difference, except when I contrived some pretty unrealistic examples.

    And this makes sense if you think about it. The GameObject class is concrete, not abstract, which is why I can create an empty one and slap a script with an Update method on it. This means that whatever abstract classes may sit above it, the GameObject class itself, has at least a stub of an Update method already, which you are overloading if you add your own. If its method were virtual, and there was just a placeholder in the VTABLE for an Update method, then this advice would make a lot more sense to me, since VTABLE lookups are pretty expensive. But since the class is concrete, you are paying for the stack traversal already to do the VTABLE lookup every time the Update stub of that GameObject instance is called, and it is called regardless of whether you have overloaded it . Overloading it with your own Update method, is not costless since there is still a call to the parent update method you are overloading, but that expense if pretty insignificant. I encourage folks to test this one out. (And please don't post back an unrealistic, contrived example, to show that there is a difference. I created a few of those, too. I'm talking about a realistic test with some actual game logic.)

    And, of course, switchyard procedural code is a horror show to debug and maintain, so if it provides little benefit, it should not be done.
     
  5. ReJ

    ReJ

    Unity Technologies

    Joined:
    Nov 1, 2008
    Posts:
    378
    Originally this advice came was due to the fact that engine to "script" calls (calls made by the Engine from C++ code into the Mono land) were slow in 1.0 and 1.0.1. Since then we employed number of optimizations to significantly improve it.

    Nowadays Update(), FixedUpdate(), etc can have some overhead comparing with calls inside the scripts, however such overhead is quite minor. Therefore avoiding such functions at the expense of employing complicated code should not gain you any improvement in performance.
     
  6. nikko

    nikko

    Joined:
    Mar 20, 2009
    Posts:
    436
    Got some more :
    - do not use several materials for one mesh. I got the problem with a little model that had 2 materials. It was taking 2 time more polys! When i removed one material, it was showing at half his the size. I don't know why. Usually each material = 1 draw call, so use texture atlas!

    - Use polygon cruncher to lower your mesh polys but even if your mesh look a little square, you can compensate this by tweaking "smoothing angle" in the importer inspector. For a highly optimized mesh, change the smoothing angle to 80 or even 120 and you mesh will look more smooth even with just a few polygons.

    - try not to use any expensive arithmetic like sin() or cos(). instead fill an array with precalculated values with x=0 to 360) sin(x) and cos(x) and use the values of this table. Of course it is in the case of you don't need precision.

    - force integer when possible. If you have a counter or check the position on the screen, declare the variable as INT, they are faster.
     
  7. Eric5h5

    Eric5h5

    Volunteer Moderator Moderator

    Joined:
    Jul 19, 2006
    Posts:
    32,401
    The number of polygons is not reported correctly when more than one material per mesh is used. However, it's still true that each material uses another draw call, so regardless of the number of polygons, it's faster to use one material only.

    --Eric
     
  8. ReJ

    ReJ

    Unity Technologies

    Joined:
    Nov 1, 2008
    Posts:
    378
    Just FYI this is fixed in Unity iPhone 1.1 and (I believe) in Unity 2.5.1.
     
  9. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    Thanks for the information, Rej. That advice certainly supports what I see currently from a performance testing standpoint. Being relatively new to the forum and Unity, I wasn't aware of the history. Although, I must say I still see the "one update only" advice being handed. Your confirmation will certainly help folks make more informed decisions.
     
  10. HanulTech

    HanulTech

    Joined:
    Apr 5, 2009
    Posts:
    312
    I suppose we should add using the "Force vertex" rendering mode on lighting. That seems to give a very significant performance gain over pixel based rendering. And on the iPhone, the difference in quality doesn't appear to be great.
     
  11. Crazy Robot

    Crazy Robot

    Joined:
    Apr 18, 2009
    Posts:
    921

    So, having a few of your scripts access one Update(), does not gain you much at all, compared to each one having it's own update? Also, I removed the update on an enemy looking for the player and instead put an InvokeRepeating instead. Does this gain me anything?
     
  12. ader

    ader

    Joined:
    Jan 27, 2009
    Posts:
    155
    ReJ/HanulTech,

    that's interesting (and good news) to know that the downside of having many update calls has been at least minimised somewhat in 1.0.2+

    However, do ReJ' observations r.e. the GameObject class having stub methods for update etc. mean that we could theoretically get a significant performance improvement if the GameObject class didn't have these stub methods?

    If so, could this perhaps become optional in a future release?