What's happening under the covers when you access the transform?

Greetings,

In Unity 5, Unity announced that:

However, if you you look at the decompiled code for the transform property you see this:

  /// <summary>
  ///   <para>Base class for everything attached to GameObjects.</para>
  /// </summary>
  [RequiredByNativeCode]
  [NativeClass("Unity::Component")]
  [NativeHeader("Runtime/Export/Scripting/Component.bindings.h")]
  public class Component : Object
  {
    /// <summary>
    ///   <para>The Transform attached to this GameObject.</para>
    /// </summary>
    public extern Transform transform { [FreeFunction("GetTransform", HasExplicitThis = true, ThrowsException = true), MethodImpl(MethodImplOptions.InternalCall)] get;
}

Based on this IL for a method to call rotate on a transform it looks like when the transform property is called in a Component class in Unity, it makes an external call to C++.

    .method public hidebysig
        instance void MyMethod2 () cil managed
    {
        // Method begins at RVA 0x2826
        // Code size 27 (0x1b)
        .maxstack 8

        // base.transform.Rotate(0f, 0f, 0f);
        IL_0000: ldarg.0
        IL_0001: call instance class [UnityEngine.CoreModule]UnityEngine.Transform [UnityEngine.CoreModule]UnityEngine.Component::get_transform()
        IL_0006: ldc.r4 0.0
        IL_000b: ldc.r4 0.0
        IL_0010: ldc.r4 0.0
        IL_0015: callvirt instance void [UnityEngine.CoreModule]UnityEngine.Transform::Rotate(float32, float32, float32)
        // }
        IL_001a: ret
    } // end of method ForIL::MyMethod2

Specifically, the [MethodImpl(MethodImplOptions.InternalCall)] attribute on the getter for the transform property indicates that the implementation of the get method is provided by native code rather than managed code. The GetTransform function referenced by the [FreeFunction] attribute is likely implemented in native code (you pretty much never see this for managed code).

So my question is, how is this implemented under-the-covers? If the transform is indeed cached on the C# side, where/how is that done? How does this getter retrieve the transform that’s cached on the c# side?

Or, did Unity roll back this change at some point? This IL is from Unity 2021.

I’d love to hear from someone at Unity about this.

After some reading, my understanding is that .transform (and likely GetComponent as well) does rely on some caching, which is exclusively on the C++ side. There are still many cases where caching it on the C# side does give you benefits, namely if you can avoid having to access this part of the API that supposedly invokes cache. Now what the caching assumes, there are zero details on that, so I’m expecting it’s just a reference resolution.

What the people from Unity also say is that, while some of them don’t actually feel the need to cache the transforms on the C# side, it is likely you have deeper problems if you’re having issues with how fast .transform is. In other words, if that’s the case, move to DOTS, which is the only systemic solution it seems.

Or maybe I should read more and test more. As far as I can tell, benchmarks show very little variance between caching transforms on your own (C#) vs not doing it. Now I can bet these benchmarks were not really stressing it properly, but I can’t say more without actually writing these tests for myself.

(I frankly don’t care that much, all of this is very dependent on the architecture of the game and I’m a firm believer in stratified systems where only a handful of [actual Unity] transforms are ever on the hot path; I am also notoriously slow when it comes to production code because I tend to take this all into account and yet I don’t want a maintenance hell either).

3 Likes

Thanks for taking the time to reply. That’s pretty much what I’m seeing elsewhere. As for the performance differences, I [wrote my own test](http://Midnite OIl Software Tutorials / TransformBenchmarkTest · GitLab). It’s basically a standalone Windows app that allows you to change the number of gameObjects dynamically and toggle between using a cached transform and using the transform property on the component. I am seeing a pretty consistent 25% improvement in frame speed and a very measurable improvement in FPS as well when using the cached transform.

Each component has a script with two methods: One that manipulates the uncached transform and one that manipulates a cached transform.

I see differences with far fewer objects as well (e.g. 2000) but the FPS in that case is so high that you wouldn’t notice a difference. Granted this is on a high-end gaming PC with a killer graphics card. You “might” notice a difference on a lower end machine or on a mobile device.

My test app also allows you to toggle off my internal stats calculation & display so you can test using an external monitoring tool (e.g. NVIDIA GE Force Game Bar) without doing a bunch of math to contaminate the test.

I’m with you on wanting to avoid “maintenance hell”. But I feel the level of effort to cache the transform is no more than the effort it takes to delete the boiler plate code every time you create a new script (I actually edit my script templates so I don’t have to do that).

I also agree that if you’re going to have a ton objects in your game you should use DOTS.

I believe that when they say that they cache it on the C#-side, this only means that the C#-Object is cached, not that it is necessarily cached inside the (visible) C#-part of the engine. (i.e. the C#-Transform-object for the underlying CPP-Transform is cached somewhere so it does not need to be recreated upon new calls for it.).

3 Likes