Reflection With Closures and Performance

In this blog we are going to dynamically create a delegate using reflection both paying attention to performance and correctness. You can find the finished code here.

We will compare

  • Hard-coded
  • Reflection
  • Dynamic
  • Static Reflection with Constants
  • Static Reflection with Closures

Background

For another project I'm working on I had to write something like this:

var action = val => obj.Prop = val;
observable.subscribe(action);

Where I don't know the type of the obj, the name of the property nor the type of val up front. So that means I have to create the action by using reflection. An extra constraint here is performance. This action will be called hundreds of times, so it cannot be slow.

Reference

Let's use this hard-coded piece of code as a reference. This one doesn't use reflection, so this is as good as it gets.

Stopwatch sw = new Stopwatch();

//test1, hard-coded
var d = new Dummy();
Action<string> action = val => d.Text = val;
sw.Start();

for (int i = 0; i < iterations; i++)
{
  action.Invoke("value1");
}

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(d.Text);

For the sake of this blog, I created a class called Dummy which has a Text property. 100 million iterations took about 1.5 seconds. So that's the time we want to approximate.

Naïve Reflection

First up, good old reflection without any optimizations:

var d = new Dummy();
object o = d;
var propName = "Text";

action = val => o.GetType().GetProperty(propName).SetValue(o, val);

This took about 54 seconds. Yikes! But there is a lot of room for improvement here.

Better Reflection

By simply getting the PropertyInfo up front I could improve the performance significantly.

var pi = o.GetType().GetProperty(propName);
action = val => pi.SetValue(o, val);

This took about 38 seconds. A lot better, but still not great.

The Lazy Man's Approach

Why are we even using Reflection? Let's use dynamic!

dynamic dy = o;
action = val => dy.Text = val;

This took about 5 seconds. I was amazed by the result. I have been using dynamics in the past because I was too lazy to use proper reflection and that turned out to be a good thing. I did cheat here, I hard-coded the Text property, so it's not a completely fair comparison. In the end, dynamic was not flexible enough for the goal in mind. So I had to abandon it.

Static Reflection

The cool thing about static reflection is that you can make a lambda expression at runtime and then compile it. Once it's compiled it's almost as fast as the hard-coded scenario.

I started by simply compiling the setter:

Action<object, string> setter = GetCompiledSetter(o, propName);
action = val => setter(o, val);

GetCompiledSetter basically builds the following lambda expression:

(object target, string value) => ((Dummy)target).Text = value

and looks like this:

1. private static Action<T, string> GetCompiledSetter<T>(T d, string propName)
2. {
3.   var pi = d.GetType().GetProperty(propName);
4.   ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
5.   ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");

6.   Expression convertExpr = Expression.Convert(targetExp, d.GetType());
7.   MemberExpression propExp = Expression.Property(convertExpr, pi);
8.   BinaryExpression assignExp = Expression.Assign(propExp, valueExp);

9.   var setter = Expression.Lambda<Action<T, string>>
      (assignExp, targetExp, valueExp).Compile();

10.  return setter;
11.}

Line 4 and 5 define the input parameters. line 6, 7 and 8 make up the body of the expression and step 9 compiles the whole thing. The type T will object, since it's called on an object and we don't know the exact type at the time. That's why the convert in step 6 is necessary.

That already looks a lot better. Unfortunately the performance doesn't seem all that great: 6.5 seconds. And I did assume the val was of type string here.

Compile the whole lambda

To improve performance even further, I decided to not just pre-compile the setter, but the whole lambda expression.

This time I'm trying to build and compile this lambda:

val => obj.Property = val 
1. private static Action<TValue> GetCompiledLambda<TTarget, TValue>(TTarget d, string propName)
2. {
3.   var pi = d.GetType().GetProperty(propName);
4.   ParameterExpression valueExp = Expression.Parameter(typeof(TValue), "val");

5.   MemberExpression propExp = Expression.Property(Expression.Constant(d), pi);
6.   BinaryExpression assignExp = Expression.Assign(propExp, valueExp);

7.   var lamb = Expression.Lambda<Action<TValue>>
      (assignExp, valueExp).Compile();
 
8.   return lamb;
9. }

Here TTarget is object and TValue is string. Step 4 produces the input, step 5 and 6 produce the body of the lambda. The performance boost is significant here: 1.5 seconds. It's almost identical to the hard-coded scenario!

But when it's too good to be true, it usually is. There is a deep dark secret in this lambda. Notice that I used Constant here. But is it really a constant? The value is captured from its surrounding context, so in fact, it's a closure. In the project that I'm building, it actually doesn't matter because the object will not change after it has been fed to the lambda. But I wondered what I would have to do to get it right.

Final Attempt

I was struggling with the creation of this lambda with a closure. So after a while I decided to cheat and do some reverse-engineering.

Action<string> exp = val => d.Text = val;

The expression above is the hard-coded piece of code from the very beginning. My plan was to turn this into an expression tree and analyse it.

But then I saw this:

expression error

It turns out that there is no way to write this as an expression tree! After a night of crying the solution came to me. Maybe I can make a factory that will produce this lambda on the fly. That means I need something like this:

(TTarget target) => (TValue val) => target.Prop = val 

And then I simply have to call it with my object to be captured. I ended up with this code:

1. private static Action<TValue> GetCompiledLambdaWithFactory<TTarget, TValue>(TTarget target, string propName)
2. {
3.   var targetExp = Expression.Parameter(target.GetType(), "target"); //not using TTarget, want specific type
4.   var valueExp = Expression.Parameter(typeof(TValue), "val");

5.   var pi = target.GetType().GetProperty(propName);
6.   MemberExpression propExp = Expression.Property(targetExp, pi);

7.   var lamb = Expression.Lambda(
                  Expression.Lambda<Action<TValue>>(
                    Expression.Assign(propExp, valueExp),
                  valueExp),
                targetExp);

8.   var factory = lamb.Compile();
9.   var inner = (Action<TValue>)factory.DynamicInvoke(target);

10.  return inner;
11.}

Line 7 is where the factory lambda is created. On line 9, I call it with my target. And voila, there is compiled expression that I wanted. And with a time of 1.6 seconds I was more than pleased with the result.

This was the final score:

Method Time (ms)
Hard-coded 1512
Bad Reflection 54062
Better Reflection 38274
Dynamic 5267
Compiled Setter 7069
Compiled Lambda with Constant 1461
Compiled Lambda with Closure 1586

Once again, you can find the finished code here.