Object.GetType()的性能

时间:2008-12-09 16:20:26

标签: c# .net performance

我们的应用程序中有很多日志记录调用。我们的记录器采用System.Type参数,因此它可以显示创建调用的组件。有时,当我们受到困扰时,我们会做类似的事情:

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

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

因为这需要只获取一次Type对象。但是,我们没有任何实际指标。任何人都知道,每次我们记录时,这节省了多少.GetType()?

(我意识到我可以自己完成指标并没有什么大问题,但是嘿,什么是StackOverflow?)

7 个答案:

答案 0 :(得分:82)

我强烈怀疑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;
    }
}

答案 1 :(得分:17)

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");

所以,我认为你的实施不会有太大的好处。

答案 2 :(得分:9)

我怀疑你会在这个问题上得到满意的答案。原因是性能,特别是这种类型的场景,是高度特定于应用程序的。

有人可能会用一个快速的秒表示例回复,其原始毫秒数会更快。但坦率地说,这对您的申请没有任何意义。为什么?它在很大程度上取决于特定场景的使用模式。例如......

  1. 你有几种类型?
  2. 你的方法有多大?
  3. 你是为每种方法做的,还是只做大的?
  4. 这些只是一些会大大改变直线时间基准相关性的问题。

答案 3 :(得分:2)

就应用程序性能而言,差异可能微不足道。但是缓存类型的第一种方法应该更快。我们去试试吧。

此代码将向您显示差异:

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毫秒,约为第二个2200毫秒。

(纠正了代码和时间 - doh!)

答案 4 :(得分:2)

您是否考虑过使用@flying-sheep运营商?

答案 5 :(得分:0)

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

答案 6 :(得分:-1)

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

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

使用const时,空循环变慢,但是缓冲的var测试变得更快。
一项更改应该不影响所有测试或仅影响2。

每次测试的周期:100000000

使用静态循环:

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

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

使用const循环:

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

Clean overview:
Object.GetType : 255
TypeOf(Class)  : 522
Type var       : -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;
        }
    }
}