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!
Code (csharp): using UnityEngine; using System.Collections; using System.Diagnostics; public class CacheTest : CachedMB { const int ITERATIONS = 1000000; // Use this for initialization Transform cached; IEnumerator Start() { cached = uncachedTransform; for (; ; ) { yield return null; if (!Input.GetKeyDown(KeyCode.T)) continue; var sw1 = Stopwatch.StartNew(); { Transform trans1; for (int i = 0; i < ITERATIONS; i++) trans1 = GetComponent<Transform>(); } sw1.Stop(); var sw2 = Stopwatch.StartNew(); { Transform trans2; for (int i = 0; i < ITERATIONS; i++) trans2 = transform; } sw2.Stop(); var sw3 = Stopwatch.StartNew(); { Transform trans3; for (int i = 0; i < ITERATIONS; i++) trans3 = cached; } sw3.Stop(); var sw4 = Stopwatch.StartNew(); { Transform trans4; for (int i = 0; i < ITERATIONS; i++) trans4 = uncachedTransform; } sw4.Stop(); print(ITERATIONS + " iterations"); print("GetComponent " + sw1.ElapsedMilliseconds + "ms"); print("MonoBehaviour " + sw4.ElapsedMilliseconds + "ms"); print("CachedMB " + sw2.ElapsedMilliseconds + "ms"); print("Manual Cache " + sw3.ElapsedMilliseconds + "ms"); } } } 1Mn Iterations GetComponent = 619ms Monobehaviour = 60ms CachedMB = 8ms Manual Cache = 3ms
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) ?
This inherits from CachedMB which does have a declaration for uncachedTransform See the second link in OP.
@NPSF3000: oh. Right. Thanks. @Jessy: Code (csharp): print(ITERATIONS + " iterations"); print("GetComponent " + sw1.ElapsedMilliseconds + "ms"); print("MonoBehaviour " + sw4.ElapsedMilliseconds + "ms"); print("CachedMB " + sw2.ElapsedMilliseconds + "ms"); print("Manual Cache " + sw3.ElapsedMilliseconds + "ms");
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
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 ----- 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.
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): var sw2 = Stopwatch.StartNew(); { Transform trans2; for (int i = 0; i < ITERATIONS; i++) trans2 = _transform ?? (_transform = base.transform); //trans2 = transform; } sw2.Stop(); 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
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): trans2 = _transform ?? (_transform = base.transform); Should be: Code (csharp): trans2 = _transform ?? (_transform = uncachedTransform);
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.
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.
Code (csharp): _transform ?? (_transform = base.transform); _transform is always null, why does it failed to work? I am using Unity 4.3.2
??-bug is alive again. Change it to Code (csharp): if (_transform == null) { _transform = base.transform; }