Performance of Object.GetType()

前端 未结 7 1798
花落未央
花落未央 2020-12-05 03:44

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

相关标签:
7条回答
  • 2020-12-05 04:17

    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.

    0 讨论(0)
  • 2020-12-05 04:18

    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!)

    0 讨论(0)
  • 2020-12-05 04:19

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

    0 讨论(0)
  • 2020-12-05 04:20

    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;
        }
    }
    
    0 讨论(0)
  • 2020-12-05 04:20

    Have you considered using nameof operator?

    0 讨论(0)
  • 2020-12-05 04:20

    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;
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题