Search Unity

CachedMB

Discussion in 'Scripting' started by npsf3000, Apr 1, 2012.

  1. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    One person mentioned 'Caching your transform' one time too many... and I snapped.

    So I created a code snippet:

    http://pastebin.com/h94YYyu7

    Then used it:

    http://pastebin.com/vYKK4pat

    In my very limited testing, 'transform' lookup went from 60ms [Monobehaviour] to 12ms [CachedMB] over 1Mn iterations. This is slightly worse than normal caching in awake [4ms] - but that's probably got to do with the null check [feel free to remove if you're game].

    Will only cache a reference if it's not null.

    Certainly not tested properly, may introduce subtle bugs, and better alternatives may exist that have not been considered. In short, I didn't spend a lot of time on this.

    Have fun!
     
    GibTreaty likes this.
  2. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Diagnostics;
    5.  
    6. public class CacheTest : CachedMB
    7. {
    8.     const int ITERATIONS = 1000000;
    9.     // Use this for initialization
    10.  
    11.     Transform cached;
    12.  
    13.     IEnumerator Start()
    14.     {
    15.         cached = uncachedTransform;
    16.  
    17.         for (; ; )
    18.         {
    19.             yield return null;
    20.             if (!Input.GetKeyDown(KeyCode.T)) continue;
    21.  
    22.             var sw1 = Stopwatch.StartNew();
    23.             {
    24.                 Transform trans1;
    25.                 for (int i = 0; i < ITERATIONS; i++)
    26.                     trans1 = GetComponent<Transform>();
    27.             } sw1.Stop();
    28.  
    29.             var sw2 = Stopwatch.StartNew();
    30.             {
    31.                 Transform trans2;
    32.                 for (int i = 0; i < ITERATIONS; i++)
    33.                     trans2 = transform;
    34.             } sw2.Stop();
    35.  
    36.  
    37.             var sw3 = Stopwatch.StartNew();
    38.             {
    39.                 Transform trans3;
    40.                 for (int i = 0; i < ITERATIONS; i++)
    41.                     trans3 = cached;
    42.             } sw3.Stop();
    43.  
    44.             var sw4 = Stopwatch.StartNew();
    45.             {
    46.                 Transform trans4;
    47.                 for (int i = 0; i < ITERATIONS; i++)
    48.                     trans4 = uncachedTransform;
    49.             } sw4.Stop();
    50.  
    51.             print(ITERATIONS + " iterations");
    52.             print("GetComponent " + sw1.ElapsedMilliseconds + "ms");
    53.             print("MonoBehaviour " + sw4.ElapsedMilliseconds + "ms");
    54.             print("CachedMB " + sw2.ElapsedMilliseconds + "ms");
    55.             print("Manual Cache " + sw3.ElapsedMilliseconds + "ms");
    56.         }
    57.     }
    58. }
    59.  
    1Mn Iterations
    • GetComponent = 619ms
    • Monobehaviour = 60ms
    • CachedMB = 8ms
    • Manual Cache = 3ms
     
    Last edited: Apr 1, 2012
  3. Tobias J.

    Tobias J.

    Joined:
    Feb 21, 2012
    Posts:
    423
    That's very useful info, thanks.

    But I don't understand how it can even run. Should't this throw an error: cached = uncachedTransform; (no declaration of uncachedTransform) ?
     
  4. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    This inherits from CachedMB which does have a declaration for uncachedTransform :)

    See the second link in OP.
     
  5. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    Where are these terms defined?
     
  6. Tobias J.

    Tobias J.

    Joined:
    Feb 21, 2012
    Posts:
    423
    @NPSF3000:
    oh. Right. Thanks.

    @Jessy:
    Code (csharp):
    1.  
    2. print(ITERATIONS + " iterations");
    3. print("GetComponent " + sw1.ElapsedMilliseconds + "ms");
    4. print("MonoBehaviour " + sw4.ElapsedMilliseconds + "ms");
    5. print("CachedMB " + sw2.ElapsedMilliseconds + "ms");
    6. print("Manual Cache " + sw3.ElapsedMilliseconds + "ms");
    7.  
     
  7. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    That's probably due to the fact, that "manual cache" is a member variable of the class, no virtual calls or property getter/setters to call
     
  8. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    I reckon it's due to the ??. Remembering that this all in the same class... so there's no reason it's not inlined etc. That said, feel free to test :p

    -----

    To all, please read the OP! The second post only contains test code!

    My concern with the code is that if any component is removed... the cache may not reflect said change. This isn't a problem with transform, but with others [e.g. guiText, audio] etc. it's a definite possibility. If only unity had a nice event system...

    I attempted to use WeakReferance... but the performance hit rendered it useless in this particular case.
     
    Last edited: Apr 2, 2012
  9. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    Well I did it, cause I was bored.

    At the beginning I got
    CachedMB: 8ms
    Manual Cache: 3ms

    I just moved your test script code into the CachedMB, same results as above. Then I in-lined it manually

    Code (csharp):
    1.  
    2.             var sw2 = Stopwatch.StartNew();
    3.             {
    4.                 Transform trans2;
    5.                 for (int i = 0; i < ITERATIONS; i++)
    6.                     trans2 = _transform ?? (_transform = base.transform);
    7.                     //trans2 = transform;
    8.             } sw2.Stop();
    9.  
    And got:
    CachedMB: 5ms
    Manual: 3ms

    So it doesn't seem Unity/Mono is in-lining it. Well I don't think neither of the three (MonoBehaviour.transfrom, CachedMB or manual caching) will make too much difference on PC/Macs unless you have many scripts which use it on Update/LateUpdate/FixedUpdate/OnGUI.

    The main issue is mobile development I think, where the CPU is much weaker than we have on PC/Macs and even with a few 1000 calls per frame it may cause a significant performance slowdown.

    edit:
    On my HTC Desire I get:
    Values are normal @1 GHz / overclocked @1.15GHz
    GetComponent: 6365 / 5493 (ouch)
    MonoBehaviour: 343 / 280
    CachedMB: 31 / 28
    Manual Cache: 17 / 12
     
    Last edited: Apr 2, 2012
  10. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    Ya.

    The problem isn't that MB is particularly slow [though it could be better as I've proven] but that it makes a call to the C++ side of the engine*. Goodbye [hardware] cache!

    [In case you haven't caught on, destroying a cache is a bad idea - in theory the actual performance cost could be far worse then what is demonstrated here because this test forces the cache to be somewhat optimal by repeated calling].

    *[Or at least, that is what I gathered from some reflection.]

    Nice work. However there is a small error:

    Code (csharp):
    1. trans2 = _transform ?? (_transform = base.transform);
    Should be:

    Code (csharp):
    1. trans2 = _transform ?? (_transform = uncachedTransform);
     
    Last edited: Apr 2, 2012
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Be careful with ?? operator, sometimes it doesn't work as expected in Unity.
     
  12. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    Can you provide more info?
     
  13. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Well that's a surprise. I always assumed that transform would be a direct reference, and to be honest can't see why it's not.

    Cheers for posting the info, NPSF3000.
     
  14. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    http://forum.unity3d.com/threads/78647-C-operator

    I think, it's because UnityEngine.Object overloads == and != operators, and when you try to compare a Unity object to null something else happens instead. And the most confusing thing is that for some magical reason ??-operator's behavior can differ in editor and standalone build.

    I wrote a bug report a year ago and the case hasn't been closed yet. I made some tests several months ago and ??'s behavior seemed to be correct, however I tried your script yesterday and got a bunch of NullReferenceExceptions in my project. After I had changed ?? into if (x == null) the exceptions disappeared.
     
    Last edited: May 16, 2012
  15. ofusion

    ofusion

    Joined:
    Dec 5, 2013
    Posts:
    27
    Code (csharp):
    1. _transform ?? (_transform = base.transform);
    _transform is always null, why does it failed to work? I am using Unity 4.3.2
     
    Last edited: Apr 22, 2014
  16. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    ??-bug is alive again. Change it to
    Code (csharp):
    1. if (_transform == null)
    2. {
    3.     _transform = base.transform;
    4. }
    5.