Efficiency of C#'s typeof operator (or whatever its representation is in MSIL)
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 开发者_StackOverflowstoring 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...
- ... a reference to the actual type object? (good)
- ... a method call/subroutine/whatever that retrieves a reference to the actual type object? (bad)
- ... a method call/subroutine/whatever that constructs a type object and returns a reference to it? (very, very bad)
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.
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.
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.
精彩评论