Выполнение Object.GetType()

В нашем приложении много вызовов регистрации. Наш регистратор принимает параметр System.Type, чтобы он мог показать, какой компонент создал вызов. Иногда, когда мы можем беспокоиться, мы делаем что-то вроде:

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

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

Поскольку для этого требуется получить объект Type только один раз. Однако у нас нет никаких реальных показателей. Кто-нибудь понял, насколько это экономится при вызове this.GetType() при каждом входе в систему?

(Я понимаю, что сам мог выполнять метрики без больших проблем, но эй, что StackOverflow для?)

Ответ 1

Я сильно подозреваю, что GetType() займет значительно меньше времени, чем любое фактическое ведение журнала. Конечно, есть вероятность, что ваш вызов Logger.Log не будет делать никаких реальных IO... Я все еще подозреваю, что разница будет неактуальной, хотя.

РЕДАКТИРОВАТЬ: Код контрольной точки находится внизу. Результаты:

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

Это вызов метода 100 миллионов раз - оптимизация набирает пару секунд или около того. Я подозреваю, что реальный метод ведения журнала будет намного больше работать, и называть 100 миллионов раз потребуется намного больше, чем 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 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 mimic that here
    [MethodImpl(MethodImplOptions.NoInlining)]
    static int Log(Type type)
    {
        return 1;
    }
}

Ответ 2

Функция 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");

Итак, я не ожидаю большого выигрыша в вашей реализации.

Ответ 3

Я сомневаюсь, что вы получите удовлетворительный ответ от SO по этому вопросу. Причина в том, что производительность, особенно сценарии этого типа, очень специфичны для приложений.

Кто-то может отправить назад с помощью быстрого секундомера, пример которого будет быстрее с точки зрения сырых миллисекунд. Но, откровенно говоря, это ничего не значит для вашего приложения. Зачем? Это сильно зависит от схемы использования вокруг этого конкретного сценария. Например...

  • Сколько у вас типов?
  • Насколько велики вы методы?
  • Вы делаете это для каждого метода или только для больших?

Это лишь некоторые из вопросов, которые значительно изменят актуальность теста времени.

Ответ 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 миллисекунд для первого подхода и ок. 2200 миллисекунд для второго.

(исправлены код и тайминги - doh!)

Ответ 5

использование поля - лучший способ и избежать внутренней блокировки словаря, вызывающей typeof() и GetType(), чтобы сохранить уникальную ссылку.

Ответ 6

Рассматривали ли вы использование nameof оператор?