Выполнение 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 оператор?