It’s common knowledge that reflection in .NET is slow, but why is that the case? This post aims to figure that out by looking at what reflection does under-the-hood.
CLR Type System Design Goals
But first it’s worth pointing out that part of the reason reflection isn’t fast is that it was never designed to have high-performance as one of its goals, from Type System Overview - ‘Design Goals and Non-goals’:
Goals
- Accessing information needed at runtime from executing (non-reflection) code is very fast.
- Accessing information needed at compilation time for generating code is straightforward.
- The garbage collector/stackwalker is able to access necessary information without taking locks, or allocating memory.
- Minimal amounts of types are loaded at a time.
- Minimal amounts of a given type are loaded at type load time.
- Type system data structures must be storable in NGEN images.
Non-Goals
- All information in the metadata is directly reflected in the CLR data structures.
- All uses of reflection are fast.
and along the same lines, from Type Loader Design - ‘Key Data Structures’:
EEClass
MethodTable
data are split into “hot” and “cold” structures to improve working set and cache utilization. MethodTable
itself is meant to only store “hot” data that are needed in program steady state. EEClass stores “cold” data that are typically only needed by type loading, JITing or reflection. Each MethodTable
points to one EEClass
.
How Does Reflection Work?
So we know that ensuring reflection was fast was not a design goal, but what is it doing that takes the extra time?
Well, there several things that are happening. To illustrate this, let's look at the managed and unmanaged code call-stack that a reflection call goes through.
System.Reflection.RuntimeMethodInfo.Invoke(..)
- Source code link
- calling System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(..)
System.RuntimeMethodHandle.PerformSecurityCheck(..)
- link
- calling
System.GC.KeepAlive(..)
System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(..)
- link
- calling stub for
System.RuntimeMethodHandle.InvokeMethod(..)
- stub for
System.RuntimeMethodHandle.InvokeMethod(..)
- link
Even if you don’t click the links and look at the individual C#/CPP methods, you can intuitively tell that there’s a lot of code being executed along the way. But to give you an example, the final method, where the bulk of the work is done, System.RuntimeMethodHandle.InvokeMethod
is over 400 LOC!
But this is a nice overview, however what is it specifically doing?
Before you can invoke a field/property/method via reflection, you have to get the FieldInfo/PropertyInfo/MethodInfo
handle for it, using code like this:
Type t = typeof(Person);
FieldInfo m = t.GetField("Name");
As shown in the previous section, there’s a cost to this, because the relevant meta-data has to be fetched, parsed, etc. Interestingly enough, the runtime helps us by keeping an internal cache of all the fields/properties/methods. This cache is implemented by the RuntimeTypeCache
class and one example of its usage is in the RuntimeMethodInfo
class.
You can see the cache in action by running the code in this gist, which appropriately enough uses reflection to inspect the runtime internals!
Before you have done any reflection to obtain a FieldInfo
, the code in the gist will print this:
Type: ReflectionOverhead.Program
Reflection Type: System.RuntimeType (BaseType: System.Reflection.TypeInfo)
m_fieldInfoCache is null, cache has not been initialised yet
But once you’ve fetched even just one field, then the following will be printed:
Type: ReflectionOverhead.Program
Reflection Type: System.RuntimeType (BaseType: System.Reflection.TypeInfo)
RuntimeTypeCache: System.RuntimeType+RuntimeTypeCache,
m_cacheComplete = True, 4 items in cache
[0] - Int32 TestField1 - Private
[1] - System.String TestField2 - Private
[2] - Int32 <TestProperty1>k__BackingField - Private
[3] - System.String TestField3 - Private, Static
where ReflectionOverhead.Program
looks like this:
class Program
{
private int TestField1;
private string TestField2;
private static string TestField3;
private int TestProperty1 { get; set; }
}
This means that repeated calls to GetField
or GetFields
are cheaper as the runtime only has to filter the pre-existing list that’s already been created. The same applies to GetMethod
and GetProperty
, when you call them the first time the MethodInfo
or PropertyInfo
cache is built.
Argument Validation and Error Handling
But once you’ve obtained the MethodInfo
, there’s still a lot of work to be done when you call Invoke
on it. Imagine you wrote some code like this:
PropertyInfo stringLengthField =
typeof(string).GetProperty("Length",
BindingFlags.Instance | BindingFlags.Public);
var length = stringLengthField.GetGetMethod().Invoke(new Uri(), new object[0]);
If you run it, you would get the following exception:
System.Reflection.TargetException: Object does not match target type.
at System.Reflection.RuntimeMethodInfo.CheckConsistency(..)
at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(..)
at System.Reflection.RuntimeMethodInfo.Invoke(..)
at System.Reflection.RuntimePropertyInfo.GetValue(..)
This is because we have obtained the PropertyInfo
for the Length
property on the String
class, but invoked it with an Uri
object, which is clearly the wrong type!
In addition to this, there also has to be validation of any arguments you pass through to the method you are invoking. To make argument passing work, reflection APIs take a parameter that is an array of object
s, one per argument. So if you using reflection to call the method Add(int x, int y)
, you would invoke it by calling methodInfo.Invoke(.., new [] { 5, 6 })
. At run-time, checks need to be carried out on the amount and types of the values passed in, in this case to ensure that there are 2 and that they are both int
s. One down-side of all this work is that it often involves boxing which has an additional cost, but hopefully this will be minimised in the future.
Security Checks
The other main task that is happening along the way is multiple security checks. For instance, it turns out that you aren’t allowed to use reflection to call just any method you feel like. There are some restricted or ‘Dangerous Methods’, that can only be called by trusted .NET framework code. In addition to a black-list, there are also dynamic security checks depending on the current Code Access Security permissions that have to be checked during invocation.
How Much Does Reflection Cost?
So now that we know what reflection is doing behind-the-scenes, it’s a good time to look at what it costs us. Please note that these benchmarks are comparing reading/writing a property directly v via reflection. In .NET, properties are actually a pair of Get/Set
methods that the compiler generates for us, however when the property has just a simple backing field the .NET JIT inlines the method call for performance reasons. This means that using reflection to access a property will show reflection in the worse possible light, but it was chosen as it’s the most common use-case, showing up in ORMs, Json serialisation/deserialisation libraries and object mapping tools.
Below are the raw results as they are displayed by BenchmarkDotNet, followed by the same results displayed in 2 separate tables. (full Benchmark code is available)
Reading a Property (‘Get’)
Method | Mean | StdErr | Scaled | Bytes Allocated/Op |
---|
GetViaProperty | 0.2159 ns | 0.0047 ns | 1.00 | 0.00 |
GetViaDelegate | 1.8903 ns | 0.0082 ns | 8.82 | 0.00 |
GetViaILEmit | 2.9236 ns | 0.0067 ns | 13.64 | 0.00 |
GetViaCompiledExpressionTrees | 12.3623 ns | 0.0200 ns | 57.65 | 0.00 |
GetViaFastMember | 35.9199 ns | 0.0528 ns | 167.52 | 0.00 |
GetViaReflectionWithCaching | 125.3878 ns | 0.2017 ns | 584.78 | 0.00 |
GetViaReflection | 197.9258 ns | 0.2704 ns | 923.08 | 0.01 |
GetViaDelegateDynamicInvoke | 842.9131 ns | 1.2649 ns | 3,931.17 | 419.04 |
Writing a Property (‘Set’)
Method | Mean | StdErr | Scaled | Bytes Allocated/Op |
---|
SetViaProperty | 1.4043 ns | 0.0200 ns | 6.55 | 0.00 |
SetViaDelegate | 2.8215 ns | 0.0078 ns | 13.16 | 0.00 |
SetViaILEmit | 2.8226 ns | 0.0061 ns | 13.16 | 0.00 |
SetViaCompiledExpressionTrees | 10.7329 ns | 0.0221 ns | 50.06 | 0.00 |
SetViaFastMember | 36.6210 ns | 0.0393 ns | 170.79 | 0.00 |
SetViaReflectionWithCaching | 214.4321 ns | 0.3122 ns | 1,000.07 | 98.49 |
SetViaReflection | 287.1039 ns | 0.3288 ns | 1,338.99 | 115.63 |
SetViaDelegateDynamicInvoke | 922.4618 ns | 2.9192 ns | 4,302.17 | 390.99 |
So we can clearly see that regular reflection code (GetViaReflection
and SetViaReflection
) is considerably slower than accessing the property directly (GetViaProperty
and SetViaProperty
). But what about the other results, let's explore those in more detail.
Setup
First, we start with a TestClass
that looks like this:
public class TestClass
{
public TestClass(String data)
{
Data = data;
}
private string data;
private string Data
{
get { return data; }
set { data = value; }
}
}
and the following common code, that all the options can make use of:
TestClass testClass = new TestClass("A String");
Type @class = testClass.GetType();
BindingFlag bindingFlags = BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public;
Regular Reflection
First, we use regular benchmark code, that acts as our starting point and the ‘worst case’:
[Benchmark]
public string GetViaReflection()
{
PropertyInfo property = @class.GetProperty("Data", bindingFlags);
return (string)property.GetValue(testClass, null);
}
Option 1 - Cache PropertyInfo
Next up, we can gain a small speed boost by keeping a reference to the PropertyInfo
, rather than fetching it each time. But we’re still much slower than accessing the property directly, which demonstrates that there is a considerable cost in the ‘invocation’ part of reflection.
PropertyInfo cachedPropertyInfo = @class.GetProperty("Data", bindingFlags);
[Benchmark]
public string GetViaReflection()
{
return (string)cachedPropertyInfo.GetValue(testClass, null);
}
Option 2 - Use FastMember
Here we make use of Marc Gravell’s excellent Fast Member library, which as you can see is very simple to use!
TypeAccessor accessor = TypeAccessor.Create(@class, allowNonPublicAccessors: true);
[Benchmark]
public string GetViaFastMember()
{
return (string)accessor[testClass, "Data"];
}
Note that it’s doing something slighty different to the other options. It creates a TypeAccessor
that allows access to all the Properties on a type, not just one. But the downside is that, as a result, it takes longer to run. This is because internally it first has to get the delegate
for the Property you requested (in this case ‘Data
’), before fetching its value. However, this overhead is pretty small, FastMember
is still way faster than Reflection and it’s very easy to use, so I recommend you take a look at it first.
This option and all subsequent ones convert the reflection code into a delegate
that can be directly invoked without the overhead of reflection every time, hence the speed boost!
Although it’s worth pointing out that the creation of a delegate
has a cost (see ‘Further Reading’ for more information). So in short, the speed boost is because we are doing the expensive work once (security checks, etc.) and storing a strongly typed delegate
that we can use again and again with little overhead. You wouldn’t use these techniques if you were doing reflection once, but if you’re only doing it once it wouldn’t be a perforamnce bottleneck, so you wouldn’t care if it was slow!
The reason that reading a property via a delegate
isn’t as fast as reading it directly is because the .NET JIT won’t inline a delegate
method call like it will do with a Property
access. So with a delegate
we have to pay the cost of a method call, which direct access doesn’t.
Option 3 - Create a Delegate
In this option, we use the CreateDelegate
function to turn our PropertyInfo
into a regular delegate
:
PropertyInfo property = @class.GetProperty("Data", bindingFlags);
Func<TestClass, string> getDelegate =
(Func<TestClass, string>)Delegate.CreateDelegate(
typeof(Func<TestClass, string>),
property.GetGetMethod(nonPublic: true));
[Benchmark]
public string GetViaDelegate()
{
return getDelegate(testClass);
}
The drawback is that you to need to know the concrete type at compile-time, i.e., the Func<TestClass, string>
part in the code above (no you can’t use Func<object, string>
, if you do, it’ll thrown an exception!). In the majority of situations when you are doing reflection, you don’t have this luxury, otherwise you wouldn’t be using reflection in the first place, so it’s not a complete solution.
For a very interesting/mind-bending way to get round this, see the MagicMethodHelper
code in the fantastic blog post from Jon Skeet ‘Making Reflection fly and exploring delegates’ or read on for Options 4 or 5 below.
Option 4 - Compiled Expression Trees
Here, we generate a delegate
, but the difference is that we can pass in an object
, so we get round the limitation of ‘Option 4’. We make use of the .NET Expression
tree API that allows dynamic code generation:
PropertyInfo property = @class.GetProperty("Data", bindingFlags);
ParameterExpression = Expression.Parameter(typeof(object), "instance");
UnaryExpression instanceCast =
!property.DeclaringType.IsValueType ?
Expression.TypeAs(instance, property.DeclaringType) :
Expression.Convert(instance, property.DeclaringType);
Func<object, object> GetDelegate =
Expression.Lambda<Func<object, object>>(
Expression.TypeAs(
Expression.Call(instanceCast, property.GetGetMethod(nonPublic: true)),
typeof(object)),
instance)
.Compile();
[Benchmark]
public string GetViaCompiledExpressionTrees()
{
return (string)GetDelegate(testClass);
}
Full code for the Expression
based approach is available in the blog post Faster Reflection using Expression Trees
Option 5 - Dynamic Code-gen with IL Emit
Finally, we come to the lowest-level approach, emiting raw IL, although ‘with great power, comes great responsibility’:
PropertyInfo property = @class.GetProperty("Data", bindingFlags);
Sigil.Emit getterEmiter = Emit<Func<object, string>>
.NewDynamicMethod("GetTestClassDataProperty")
.LoadArgument(0)
.CastClass(@class)
.Call(property.GetGetMethod(nonPublic: true))
.Return();
Func<object, string> getter = getterEmiter.CreateDelegate();
[Benchmark]
public string GetViaILEmit()
{
return getter(testClass);
}
Using Expression
tress (as shown in Option 4), doesn’t give you as much flexibility as emitting IL codes directly, although it does prevent you from emitting invalid code! Because of this, if you ever find yourself needing to emit IL, I really recommend using the excellent Sigil library, as it gives better error messages when you get things wrong!
Summary
The take-away is that if (and only if) you find yourself with a performance issue when using reflection, there are several different ways you can make it faster. These speed gains are achieved by getting a delegate
that allows you to access the Property
/Field
/Method
directly, without all the overhead of going via reflection every-time.
Further Reading
For reference, below is the call-stack or code-flow that the runtime goes through when Creating a Delegate.
Delegate CreateDelegate(Type type, MethodInfo method)
Delegate CreateDelegate(Type type, MethodInfo method, bool throwOnBindFailure)
Delegate CreateDelegateInternal(RuntimeType rtType, RuntimeMethodInfo rtMethod, Object firstArgument, DelegateBindingFlags flags, ref StackCrawlMark stackMark)
Delegate UnsafeCreateDelegate(RuntimeType rtType, RuntimeMethodInfo rtMethod, Object firstArgument, DelegateBindingFlags flags)
bool BindToMethodInfo(Object target, IRuntimeMethodInfo method, RuntimeType methodType, DelegateBindingFlags flags);
FCIMPL5(FC_BOOL_RET, COMDelegate::BindToMethodInfo, Object* refThisUNSAFE, Object* targetUNSAFE, ReflectMethodObject *pMethodUNSAFE, ReflectClassBaseObject *pMethodTypeUNSAFE, int flags)
COMDelegate::BindToMethod(DELEGATEREF *pRefThis, OBJECTREF *pRefFirstArg, MethodDesc *pTargetMethod, MethodTable *pExactMethodType, BOOL fIsOpenDelegate, BOOL fCheckSecurity)
The post Why is reflection slow? first appeared on my blog Performance is a Feature!
CodeProject