19

I know premature optimization is the mother of all evil. However, I am defining a generic method that uses Reflection to retrieve its generic type's metadata, and would like to know whether calling typeof(T) several times as in the following code snippet:

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

... is less efficient than storing the type object into a variable as in the following code snippet:

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

...?

If I understand compiler theory correctly (and I think I do), this question could be reduced to the following one:

When the JIT compiler instantiates a generic type, does it replace every instance of [whatever the MSIL representation of typeof(T) is] with...

  1. ... a reference to the actual type object? (good)
  2. ... a method call/subroutine/whatever that retrieves a reference to the actual type object? (bad)
  3. ... a method call/subroutine/whatever that constructs a type object and returns a reference to it? (very, very bad)
isekaijin
  • 19,076
  • 18
  • 85
  • 153
  • 1
    Did you try to look at the resulting IL? What did you learn from it? Have you tried benchmarking? What were the results? – viraptor Jun 20 '11 at 21:30
  • Definite not #3 as the references are all the same `Type` object for the same type. – BoltClock Jun 20 '11 at 21:33
  • @viraptor: I don't know IL. // When I write in C++, I don't think of the x86 or x86_64 code it is going to generate for me. I just follow the concepts. – isekaijin Jun 20 '11 at 21:33
  • @Eduardo, if you are only concerned about actual performance, why don't you try it? You have both codes, you can measure them. – svick Jun 20 '11 at 21:40
  • 1
    Also, IL is much more readable than actual assembly. And using Reflector or dotPeek, you can see the actual generated code in the form very similar to C# code. – svick Jun 20 '11 at 21:42
  • @svick: I am not only concerned about performance. I am concerned about "purity", whatever that means. (I know, it's kind of impractical, but, believe it or not, once I know what the "pure" approach is, mindlessly following it actually results in efficient programs that can be delivered in a short time.) – isekaijin Jun 20 '11 at 21:45
  • @Eduardo, I have no idea what you mean by “pure”. “Pure method” usually means a method that has no side-effects, but that doesn't seem to be what you mean. – svick Jun 20 '11 at 21:56
  • @svick: I didn't mean "pure" as in "pure function". I meant "pure" as in "implemented following a definition, not tuned to be efficient in any specific platform/framework/library." – isekaijin Jun 20 '11 at 22:02

3 Answers3

6

A little intuition should tell you that declaring a single instance of a variable to hold the result of GetType(), which is then used throughout the rest method would be more efficient (and more readable to boot)

Here is the IL of the two methods:

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

You save 1 or 2 calls to System.Type.GetTypeFromHandle by declaring it in the local variable. I'm not certain that the JIT'ing process won't compile those out, but I would personally put more faith in the compiler to optimize the IL for things like over the JIT'er, but that's just me.

Brandon Moretz
  • 7,512
  • 3
  • 33
  • 43
  • To my knowledge, the C# compiler does relatively few optimizations - most optimizations are delegated and performed by the JIT compiler. – LBushkin Jun 21 '11 at 04:58
  • 1
    @LBushkin In this particular case I believe it makes more sense that the IL compiler would optimize this away before it got to the JIT'ing process, given that it's job is to reduce the load on the native compiler and this has no relevance to the native representation of the code. However, my point was: "Why make things harder on either compiler when a little programmers intuition can help you avoid cases like this all together?" – Brandon Moretz Jun 21 '11 at 13:29
5

The generated MSIL shows the two are different, with typeof(T) not being lifted into a local variable. This means it will load T's type metadata onto the stack and call Type.GetTypeFromHandle on it at each and every usage. I don't know why it chose not to lift this with /optimize+, but I consider that to be the compiler's prerogative.

One practical difference between your two code blocks is that typeof(T) is basically a constant expression whereas your local variable type is mutable. This may not be the intended semantics which a future developer could break.

user7116
  • 63,008
  • 17
  • 141
  • 172
4

I don't know if this is a documented behavior of either the C# or JIT compilers - relying on undocumented behaviors in critical matters is generally not a great idea. In principle, this is a version of the constant propagation problem (since T can't change in the scope of the method), and an optimizing compiler should be able to figure that out.

If the performance is critical to you, you probably want to ensure the desired behavior by storing a local reference to the type information.

If you're simply curious, I would recommend reading the generated IL and/or performing some benchmarks on the code to see what real-world difference such a change would have in your specific scenario.

Performance aside, I actually find the version of the code where you use a variable to refer to the type parameter to be more readable and understandable than the version where typeof(T) is repeated throughout.

LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • 4
    Performance aside, I actually prefer the opposite. Here's why: To understand the meaning of `if(t == myType)`, we need context - we first have to find the declaration of myType, then scan every place we assign a value to it before we know that the if-block is doing. In contrast, the meaning of `if(t == typeof(String))` is complete, even without any context. We have everything we need to know: t is a variable of type Type, and we enter the block when t represents a String-type. We can infer almost nothing about the first example without reading the surrounding code. – MattE_WI Jan 05 '17 at 23:19