Performance of Object.GetType()

C#.NetPerformance

C# Problem Overview


We have lots of logging calls in our app. Our logger takes a System.Type parameter so it can show which component created the call. Sometimes, when we can be bothered, we do something like:

class Foo
{
  private static readonly Type myType = typeof(Foo);

  void SomeMethod()
  {
     Logger.Log(myType, "SomeMethod started...");
  }
 }

As this requires getting the Type object only once. However we don't have any actual metrics on this. Anyone got any idea how much this saves over calling this.GetType() each time we log?

(I realise I could do the metrics myself with no big problem, but hey, what's StackOverflow for?)

C# Solutions


Solution 1 - C#

I strongly suspect that GetType() will take significantly less time than any actual logging. Of course, there's the possibility that your call to Logger.Log won't do any actual IO... I still suspect the difference will be irrelevant though.

EDIT: Benchmark code is at the bottom. Results:

typeof(Test): 2756ms
TestType (field): 1175ms
test.GetType(): 3734ms

That's calling the method 100 million times - the optimisation gains a couple of seconds or so. I suspect the real logging method will have a lot more work to do, and calling that 100 million times will take a lot longer than 4 seconds in total, even if it doesn't write anything out. (I could be wrong, of course - you'd have to try that yourself.)

In other words, as normal, I'd go with the most readable code rather than micro-optimising.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

class Test
{
    const int Iterations = 100000000;
    
    private static readonly Type TestType = typeof(Test);
    
    static void Main()
    {
        int total = 0;
        // Make sure it's JIT-compiled
        Log(typeof(Test)); 
        
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(typeof(Test));
        }
        sw.Stop();
        Console.WriteLine("typeof(Test): {0}ms", sw.ElapsedMilliseconds);
        
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(TestType);
        }
        sw.Stop();
        Console.WriteLine("TestType (field): {0}ms", sw.ElapsedMilliseconds);
        
        Test test = new Test();
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            total += Log(test.GetType());
        }
        sw.Stop();
        Console.WriteLine("test.GetType(): {0}ms", sw.ElapsedMilliseconds);
    }
    
    // I suspect your real Log method won't be inlined,
    // so let's mimic that here
    [MethodImpl(MethodImplOptions.NoInlining)]
    static int Log(Type type)
    {
        return 1;
    }
}

Solution 2 - C#

The GetType() function is marked with the special attribute [MethodImpl(MethodImplOptions.InternalCall)]. This means its method body doesn't contain IL but instead is a hook into the internals of the .NET CLR. In this case, it looks at the binary structure of the object's metadata and constructs a System.Type object around it.

EDIT: I guess I was wrong about something ...

I said that: "because GetType() requires a new Object to be build" but it seems this is not correct. Somehow, the CLR caches the Type and always returns the same object so it doesn't need to build a new Type object.

I'm based on the following test:

Object o1 = new Object();
Type t1 = o1.GetType();
Type t2 = o1.GetType();
if (object.ReferenceEquals(t1,t2))
    Console.WriteLine("same reference");

So, I don't expect much gain in your implementation.

Solution 3 - C#

I doubt you are going to get a satisfying answer from SO on this subject. The reason being that performance, especially scenarios of this type, are highly application specific.

Someone may post back with a quick stopwatch example of which would be faster in terms of raw miliseconds. But frankly that doesn't mean anything for your application. Why? It depends highly on the usage pattern around that particular scenario. For instance ...

  1. How many types do you have?
  2. How big are you methods?
  3. Do you do this for every method, or only big ones?

These are just a few of the questions that will greatly alter the relevance of a straight time benchmark.

Solution 4 - C#

Have you considered using nameof operator?

Solution 5 - C#

The difference is probably negligible as far as application performance is concerned. But the first approach where you cache the type should be faster. Let's go and test.

This code will show you the difference:

using System;

namespace ConsoleApplicationTest {
    class Program {
        static void Main(string[] args) {

            int loopCount = 100000000;

            System.Diagnostics.Stopwatch timer1 = new System.Diagnostics.Stopwatch();
            timer1.Start();
            Foo foo = new Foo();
            for (int i = 0; i < loopCount; i++) {
                bar.SomeMethod();
            }
            timer1.Stop();
            Console.WriteLine(timer1.ElapsedMilliseconds);

            System.Diagnostics.Stopwatch timer2 = new System.Diagnostics.Stopwatch();
            timer2.Start();
            Bar bar = new Bar();
            for (int i = 0; i < loopCount; i++) {
                foo.SomeMethod();
            }
            timer2.Stop();
            Console.WriteLine(timer2.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }

    public class Bar {
        public void SomeMethod() {
            Logger.Log(this.GetType(), "SomeMethod started...");
        }
    }

    public class Foo {
        private static readonly Type myType = typeof(Foo); 
        public void SomeMethod() { 
            Logger.Log(myType, "SomeMethod started..."); 
        }
    }

    public class Logger {
        public static void Log(Type type, string text) {
        }
    }
}

On my machine, this gave results of approx. 1500 milliseconds for the first approach and approx. 2200 milliseconds for the second.

(code and timings corrected - doh!)

Solution 6 - C#

using field is the best way and it avoid internal dictionary lock causing by typeof() and GetType() to keep unique reference.

Solution 7 - C#

I get very different results.
For this I created a new console app in another project, and used a class with inheritance.

I created an empty loop to withdraw from the results, for a clean comparison.
I created a const and a static for the cycles (manually switching which to use).
Something very interesting happend.

When using the const, the empty loop become slow, but the buffered var test becomes slightly faster.
A change that should affect none or all tests, only affect 2.

Cycles for each test : 100000000

Using static cycle:

Object.GetType : 1316
TypeOf(Class)  : 1589
Type var       : 987
Empty Loop     : 799

Clean overview: Object.GetType : 517 TypeOf(Class) : 790 Type var : 188

Using const cycle:
Object.GetType : 1316
TypeOf(Class)  : 1583
Type var       : 853
Empty Loop     : 1061

Clean overview: Object.GetType : 255 TypeOf(Class) : 522 Type var : -208

I ran these multiple times, and with some small changes, and with 10 times more cycles, to reduce the risk of background processes affecting results. Almost same results as these 2 above.

It does seem that Object.GetType() is 1.5-2 times as fast as typeof(class).
The buffered var seem to be 1.5-2 times as fast as Object.GetType().

I the right application, this is not just micro-optimising.
If you sacrifice small things here and there, they will easily slow more than the one big thing you made 30% faster.

Again as JaredPar answered, these kind of tests, is unreliable for telling about your specific application, as we have proven here.
All our tests giving quite different results, and things seemingly unrelated to the code at hand, can affect the performance.

The test:

.NetCore 2.1

namespace ConsoleApp1
{
    class Program
    {
        public const int Cycles = 100000000;
        public static int Cycles2 = 100000000;
        public static QSData TestObject = new QSData();
        public static Type TestObjectType;

        static void Main(string[] args)
        {
            TestObjectType = TestObject.GetType();
            Console.WriteLine("Repeated cycles for each test : " + Cycles.ToString());

            var test1 = TestGetType();
            Console.WriteLine("Object.GetType : " + test1.ToString());
            var test2 = TestTypeOf();
            Console.WriteLine("TypeOf(Class)  : " + test2.ToString());
            var test3 = TestVar();
            Console.WriteLine("Type var       : " + test3.ToString());
            var test4 = TestEmptyLoop();
            Console.WriteLine("Empty Loop     : " + test4.ToString());

            Console.WriteLine("\r\nClean overview:");
            Console.WriteLine("Object.GetType : " + (test1 - test4).ToString());
            Console.WriteLine("TypeOf(Class)  : " + (test2 - test4).ToString());
            Console.WriteLine("Type var       : " + (test3 - test4).ToString());

            Console.WriteLine("\n\rPush a button to exit");
            String input = Console.ReadLine();
        }

        static long TestGetType()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < Cycles; i++)
            {
                Type aType = TestObject.GetType();
            }
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }

        static long TestTypeOf()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < Cycles; i++)
            {
                Type aType = typeof(QSData);
            }
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }

        static long TestVar()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < Cycles; i++)
            {
                Type aType = TestObjectType;
            }
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }

        static long TestEmptyLoop()
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int i = 0; i < Cycles; i++)
            {
                Type aType;
            }
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGazView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#bruno condeView Answer on Stackoverflow
Solution 3 - C#JaredParView Answer on Stackoverflow
Solution 4 - C#michal.jakubeczyView Answer on Stackoverflow
Solution 5 - C#Sam MeldrumView Answer on Stackoverflow
Solution 6 - C#Teter28View Answer on Stackoverflow
Solution 7 - C#user305874View Answer on Stackoverflow