【问题标题】:Performance of Object.GetType()Object.GetType() 的性能
【发布时间】:2010-09-26 02:00:52
【问题描述】:

我们的应用中有很多日志调用。我们的记录器接受一个 System.Type 参数,因此它可以显示哪个组件创建了调用。有时,当我们受到打扰时,我们会做以下事情:

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

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

因为这只需要获取一次 Type 对象。但是,我们对此没有任何实际指标。有谁知道每次登录时调用 this.GetType() 可以节省多少?

(我意识到我可以自己做这些指标没有什么大问题,但是,嘿,StackOverflow 有什么用?)

【问题讨论】:

    标签: c# .net performance


    【解决方案1】:

    我得到了非常不同的结果。
    为此,我在另一个项目中创建了一个新的控制台应用程序,并使用了一个具有继承的类。

    我创建了一个空循环来退出结果,以便进行清晰的比较。
    我为循环创建了一个 const 和一个 static(手动切换要使用的)。
    发生了一件非常有趣的事情。

    使用 const 时,空循环会变慢,但缓冲的 var 测试会稍微快一些。
    一个不应该影响或所有测试的更改,只会影响 2。

    每个测试的周期:100000000

    使用静态循环:

    对象.GetType:1316 类型(类):1589 类型变量:987 空循环:799 清洁概述: 对象.GetType:517 类型(类):790 类型变量:188

    使用 const 循环:

    对象.GetType:1316 类型(类):1583 类型变量:853 空循环:1061 清洁概述: 对象.GetType:255 类型(类):522 类型变量:-208

    我多次运行这些程序,并进行了一些小的更改,并且循环次数增加了 10 倍,以降低后台进程影响结果的风险。结果与上述 2 个几乎相同。

    Object.GetType() 似乎是 typeof(class) 的 1.5-2 倍。
    缓冲的 var 似乎是 Object.GetType() 的 1.5-2 倍。

    我是正确的应用程序,这不仅仅是微优化。
    如果您在这里和那里牺牲一些小东西,它们的速度很容易比您制造的一件大东西快 30%。

    正如 JaredPar 所回答的那样,正如我们在此处所证明的那样,此类测试无法可靠地说明您的特定应用程序。
    我们所有的测试给出了完全不同的结果,并且看起来与手头的代码无关的事情可能会影响性能。

    测试:

    .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;
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      您是否考虑过使用nameof 运算符?

      【讨论】:

      • nameof() 在 2008 年还不存在,但现在将是一个不错的选择。
      【解决方案3】:

      使用字段是最好的方法,它避免了由 typeof() 和 GetType() 引起的内部字典锁定,以保持唯一的引用。

      【讨论】:

        【解决方案4】:

        就应用程序性能而言,差异可能可以忽略不计。但是缓存类型的第一种方法应该更快。我们去测试一下。

        这段代码会告诉你区别:

        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) {
                }
            }
        }
        

        在我的机器上,这给出了大约的结果。第一种方法需要 1500 毫秒,大约需要 1500 毫秒。秒为 2200 毫秒。

        (代码和时间已更正 - 哦!)

        【讨论】:

        • 尝试将第二个 foo.SomeMethod() 更改为 bar.SomeMethod()。您目前正在对同一事物进行两次基准测试 :)
        • 嗨,Sam,您这里的代码似乎不会实际运行。在foo 的循环中,您甚至在声明bar 之前就调用了bar.SomeMethod。我会建议自己进行编辑,但如果我编辑的代码未在您的基准测试中使用,那将毫无意义。
        【解决方案5】:

        GetType() 函数标有特殊属性 [MethodImpl(MethodImplOptions.InternalCall)]。这意味着它的方法体不包含 IL,而是一个挂钩到 .NET CLR 的内部。在这种情况下,它会查看对象元数据的二进制结构,并在其周围构造一个System.Type 对象。

        编辑:我想我错了……

        我说过:“因为GetType() 需要构建一个新对象”,但这似乎是不正确的。不知何故,CLR 缓存了Type 并始终返回相同的对象,因此它不需要构建新的 Type 对象。

        我基于以下测试:

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

        所以,我预计您的实施不会有太大收获。

        【讨论】:

        • 是什么让您认为它每次都在创建一个新对象?事实上,证明这不是案例是微不足道的。打印 object.ReferenceEquals(x.GetType(), x.GetType())。
        • :) 我什至在您写此评论并更正我的答案之前就这样做了。谢谢。
        【解决方案6】:

        我强烈怀疑 GetType() 将比任何实际日志记录花费的时间要少得多。当然,您对 Logger.Log 的调用有可能不会执行任何实际的 IO ......我仍然怀疑差异将是无关紧要的。

        编辑:基准代码位于底部。结果:

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

        这是调用该方法 100 百万 次 - 优化获得了几秒钟左右的时间。我怀疑真正的日志记录方法还有很多工作要做,调用这 1 亿次总共会花费超过 4 秒的时间,即使它没有写出任何内容。 (当然,我可能是错的 - 你必须自己尝试。)

        换句话说,像往常一样,我会使用最易读的代码而不是微优化。

        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;
            }
        }
        

        【讨论】:

        • 有趣的是,下面 Sam Meldrum 的基准测试给出的差异甚至更小......
        • 鉴于在我的 Eee PC(几乎不是强大的)上,他的测试仅在大约 2500 毫秒内运行,我猜他要么在运行调试版本,要么在调试器下运行。然而,无论如何,他们做的事情略有不同。注意内联等。
        • 实际上,他的测试无论如何都是假的——只是发现了一个错误。将添加评论。
        • 嗯...运行 Sam 代码的固定版本,我得到 非常 不同的结果。我怀疑在第一种情况下,几乎 所有 Sam 的代码都被优化了(优化开启。)
        • 为了避免编写我实现的样板类型缓存代码:static class TypeGetter&lt;T&gt; { public static readonly Type Type = typeof(T); } 这似乎在性能上与 TestType(字段)方法几乎等效。奇怪的是,我的基准测试结果因基准测试的顺序而出现偏差 - 两种方法(TypeGetter 和 TestType)中的任何一种运行第二次都会稍快。
        【解决方案7】:

        我怀疑你会从 SO 那里得到一个令人满意的答案。原因是性能,尤其是这种类型的场景,是高度特定于应用程序的。

        有人可能会发回一个快速秒表示例,就原始毫秒而言,该示例会更快。但坦率地说,这对您的应用程序没有任何意义。为什么?这在很大程度上取决于围绕该特定场景的使用模式。比如……

        1. 你有多少种?
        2. 你的方法有多大?
        3. 您是对每种方法都这样做,还是只对大的方法这样做?

        这些只是会大大改变直接时间基准的相关性的几个问题。

        【讨论】:

        • +1。添加一个事实,即微基准测试可以在不创建缓存未命中的情况下摆脱代码膨胀(额外变量),而在实际应用程序中缓存值可能会创建 gc 压力、缓存未命中、分页......微基准测试是无关紧要的。分析是要走的路。
        • 我同意应该谨慎使用微基准测试——但我认为在这种情况下,它们表明对 GetType() 的调用非常便宜,因此微优化不太可能有帮助。请注意,这是一个静态字段 - 那里没有太多额外的 GC 压力......
        • 进一步说明:与调用 GetType() 和不调用 GetType() 之间的差异相比,分析会改变应用程序的性能很多。这是一种自然的侵入性手术。特定于应用程序的基准测试是最好的方法。如果您无法证明优化对应用有显着的帮助...
        • ...那么几乎可以肯定不值得这样做。 “显着”必须是指至少在正常条件下可测量(即不是在完整的分析器下 - 如有必要,只需非常低影响的计时器。)
        • @Jon Skeet:我同意,但考虑到这是一个日志记录应用程序,我假设它已经过微调(微优化),因为在这种情况下,微优化是有意义的(对于延迟、吞吐量、内存等......)
        猜你喜欢
        • 2019-05-21
        • 1970-01-01
        • 1970-01-01
        • 2011-03-16
        • 2011-10-03
        • 2016-01-01
        • 1970-01-01
        • 2015-03-04
        • 2021-11-04
        相关资源
        最近更新 更多