Подтвердить что ты не робот

Эффективность оператора С# typeof (или независимо от его представления в MSIL)

Я знаю, что преждевременная оптимизация - мать всего зла. Однако я определяю общий метод, который использует Reflection для извлечения его метаданных общего типа и хотел бы знать, вызывает ли вызов typeof(T) несколько раз, как в следующем фрагменте кода:

private static Dictionary<Type, PropertyInfo[]> elementProperties;

private static T MakeElement<T>(SqlDataReader reader) where T : class, new() {
    PropertyInfo[] properties;
    if (elementProperties.ContainsKey(typeof(T)))
        properties = elementProperties[typeof(T)];
    else
        properties = elementProperties[typeof(T)] = typeof(T).GetProperties();

    // more code...
}

... менее эффективен, чем хранение объекта типа в переменной, как в следующем фрагменте кода:

private static Dictionary<Type, PropertyInfo[]> elementProperties;

private static T MakeElement<T>(SqlDataReader reader) where T : class, new() {
    PropertyInfo[] properties;
    Type type = typeof(T);
    if (elementProperties.ContainsKey(type))
        properties = elementProperties[type];
    else
        properties = elementProperties[type] = type.GetProperties();

    // more code...
}

...

Если я правильно понимаю теорию компилятора (и думаю, что знаю), этот вопрос можно было бы свести к следующему:

Когда компилятор JIT создает экземпляр типового типа, он заменяет каждый экземпляр [независимо от представления MSIL typeof(T)] с...

  • ... ссылка на объект фактического типа? (Хорошо)
  • ... вызов/подпрограмма метода/все, что получает ссылку на объект фактического типа? (Плохо)
  • ... вызов/подпрограмма метода/независимо от того, что создает объект типа и возвращает ссылку на него? (очень, очень плохо)
4b9b3361

Ответ 1

Небольшая интуиция должна сказать вам, что объявление одного экземпляра переменной для хранения результата GetType(), который затем используется во всем остальном, будет более эффективным (и более читаемым для загрузки)

Вот IL из двух методов:

MakeElement 1:

Icall       System.Type.GetTypeFromHandle
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.ContainsKey
brfalse.s   IL_002F
ldarg.0     
ldfld       elementProperties
ldtoken     01 00 00 1B 
call        System.Type.GetTypeFromHandle
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.get_Item
pop         
br.s        IL_0053
ldarg.0     
ldfld       elementProperties
ldtoken     01 00 00 1B 
call        System.Type.GetTypeFromHandle
ldtoken     01 00 00 1B 
call        System.Type.GetTypeFromHandle
call        System.Type.GetProperties
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.set_Item

MakeElement 2:

call        System.Type.GetTypeFromHandle
stloc.0     
ldarg.0     
ldfld       elementProperties
ldloc.0     
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.ContainsKey
brfalse.s   IL_0028
ldarg.0     
ldfld       elementProperties
ldloc.0     
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.get_Item
pop         
br.s        IL_003A
ldarg.0     
ldfld       elementProperties
ldloc.0     
ldloc.0     
callvirt    System.Type.GetProperties
callvirt    System.Collections.Generic.Dictionary<System.Type,System.Reflection.PropertyInfo[]>.set_Item

Вы сохраняете 1 или 2 вызова System.Type.GetTypeFromHandle, объявляя их в локальной переменной. Я не уверен, что процесс JIT'ing не будет компилировать их, но я лично поставил бы больше веры в компилятор, чтобы оптимизировать IL для таких вещей, как JIT'er, но это только я.

Ответ 2

Я не знаю, является ли это документированным поведением либо компиляторов С#, либо JIT - полагаясь на недокументированное поведение в критичных вопросах, как правило, это не отличная идея. В принципе, это версия проблемы с постоянным распространением (поскольку T не может меняться в объеме метода) и оптимизирующий компилятор должны быть в состоянии понять это.

Если производительность важна для вас, вы, вероятно, захотите обеспечить желаемое поведение, сохранив локальную ссылку на информацию о типе.

Если вам просто интересно, я бы рекомендовал прочитать сгенерированный IL и/или выполнить некоторые тесты кода, чтобы узнать, какое изменение в реальном мире может иметь такое изменение в вашем конкретном сценарии.

Производительность в стороне, я на самом деле нахожу версию кода, в которой вы используете переменную, чтобы ссылаться на параметр типа, чтобы быть более читабельным и понятным, чем версия, где typeof(T) повторяется повсюду.

Ответ 3

Сгенерированная MSIL показывает, что две разные, при этом typeof(T) не поднимается в локальную переменную. Это означает, что он загрузит метаданные типа T в стек и вызовет Type.GetTypeFromHandle на нем при каждом использовании. Я не знаю, почему он решил не поднимать это с помощью /optimize+, но я считаю, что это прерогатива компилятора.

Одно практическое различие между двумя вашими кодовыми блоками состоит в том, что typeof(T) является в основном постоянным выражением, тогда как ваша локальная переменная type изменчива. Это может быть не предполагаемая семантика, которую может сломать будущий разработчик.