Monday, January 6, 2014

Improving Reflection Performance with Delegates

Reflection is an extremely powerful tool. But one of the drawbacks is performance. In my presentations on Reflection, we look at an application that shows the speed differences between calling methods directly and calling them with Reflection (live presentation on my website; video presentation on Pluralsight).

Now, the point of this application is to show that dynamically invoking a method through reflection is 30 times slower than making a direct method call. This is to encourage us to make sure we only use reflection when we actually need it. But if we do need it, there are ways to improve the performance that I don't talk about in the presentation. Instead of doing dynamic invocation directly, we can use a delegate to improve performance.

The sample code is available here:

Baseline Speed
The sample application shows 4 different ways to call a method (as opposed to the 2 methods shown in the original presentation sample).

Here's the code for the direct method call:

Most of this code is boiler-plate to get the metrics for the UI -- and I use the word "metrics" here very loosely. This code performs the loop 10,000,000 times (which is a lot). That's how many times we need to do this so that we can get some human-noticeable times. And this just gives us a general idea. When we run this code, the computer is doing other stuff (background operations, UI updates, network polling, etc.), so the exact numbers will vary.

This important bits of the above method are the first line (where we create a new List object) and the line inside the "for" loop (where we add the indexer to the list).

When running on my machine (a dual-core i7), we get the following result:

Dynamic Invocation with Reflection
When we try the same functionality using reflection, we get a much different result. Here's the code:

This code is a little bit different. We still create the new List variable. But then, we use reflection to execute the "Add" method. To do this, we get a Type object based on List<int>. Then we call "GetMethod" which gives us back an MethodInfo object.

Then inside the loop, we call Invoke on the MethodInfo object. The parameters look a bit strange for this method. The first parameter is the instance we can to call the method on -- in this case, it is the "list" variable that we created at the top. The second parameter is an object array for the method parameters. Since we need to pass in a single parameter (an integer), we create an object array with a single value.

The result of this method call is the same as the method in the first example -- we add 10,000,000 items to a List object.

The performance is significantly different:

Instead of 127 milliseconds, we get 3.5 seconds! That's around 30 times longer.

Using an Interface
The recommendation in the Practical Reflection presentation is to use Reflection to load and instantiate an object (to give us the flexibility of run-time loading), but then cast the object to a known interface in order to call the method. This gives us the best of both worlds.

Here's that code:

At the top of this method, we get a Type object based on List<int>, and then we use the Activator class to create an instance of that type. Notice that our variable ("list") is an interface type (IList<int>) rather than a concrete type.

Because of this, even though we create the object dynamically, we can call the "Add" method just like we would on a normal object. (And we see this inside the "for" loop.)

The result is that we do not get a performance hit. It runs at the same speed as a direct method call:

Reflection with a Delegate
But there is another option as well. If we absolutely need to use Reflection to dynamically call a method multiple times, we can use a delegate to improve the performance.

Here's the code for that:

This code is a bit more complicated. Notice at the very top (outside of our button click handler), we have a definition of a delegate ("ListAddDelegate"). Notice the signature for this delegate. The first parameter is "List<int>" -- this is the instance of the list that the "Add" method is called on. The second parameter is the parameter for the "Add" method -- in this case, an integer. The delegate returns void because List<T>.Add (the method we want to call) returns void.

The first 3 lines of the button click handler match the reflection method. We create an instance of a List<int>, get a Type variable, and then use GetMethod to get a MethodInfo object.

But then we create a delegate instance. We use "Delegate.CreateDelegate" to create a delegate object based on our MethodInfo object. The first parameter is the Type of the delegate we want (our custom ListAddDelegate), and the second parameter is the MethodInfo object (the "addMethod" that we got above). Then we cast this whole thing to a ListAddDelegate.

Inside our "for" loop, we simply invoke our custom delegate by calling "addDelegate" with the 2 parameters (the List<int> instance and the integer that we want to "Add").

The result is much better performance:

This time is inline with the direct method call and the interface method call.

Here are all of the results together:

Wrap Up
The point of the original speed comparison is to show that using Reflection to call a method is 30 times slower than making a direct call. But if we do find that we need reflection to dynamically call a method multiple times, we do have the option of creating a delegate to handle the method calls.

There are several ways to come up with similar answers. As developers, we should be used to this. What we need to do is weigh the pros and cons of each approach in the context of our own application -- keeping in mind that we want to balance flexibility and performance.

Happy Coding!

No comments:

Post a Comment