Monday, November 12, 2012

using Reflection & Delegates to call the private parts of product

Short time ago I faced with a case when I need to call the private method. There are a lot of people who forget to make some methods public or just prefer internal. There are also other ones, who like to use the "private" instead of "protected". I don't want to write a lot about all these people because their places in hell have been already prepared.
So I derived from a parent class, overridden the virtual method and faced with the following issue: the overridden method calls private ones. I have 2 obvious ways how to handle this:
  1. Bring the implementation of the private method into my class;
  2. Use the Reflection.
The #1 approach is bad because... because I want to write you about the Reflection :)
actually I hope you know why it's unacceptable.


Before I start to describe the second option, I want to provide you with a simple code, which illustrates the issue. All the examples will be created on top of this code.
 
    public class BaseClass
    {
        private void EmptyGood(object arg1, int arg2)
        { }

        private void EmptyBad(object arg1, int arg2)
        { }

        public virtual void PublicWorker(object arg)
        {
            EmptyBad(arg, 1);
            EmptyGood(arg, 2);
        }
    }

    public class ChildClass:BaseClass
    {
        private void EmptyBadFixed(object arg1,int arg2)
        { }

        //we need prevent the bad method from calling
        public override void PublicWorker(object arg)
        {
            EmptyBadFixed(arg, 1);

            //here we should call parent's good method
        }
    }
The code is pretty straightforward. In our overridden method we need to call the fixed EmptyMethodBad() method and EmptyGood() one from the parent class. But it's private. Here we start to play.

BASE REFLECTION
----
It's no problem at all! I know what is the Reflection and I know how to manage life. A few trivial lines of code and method is invoked:
 public override void PublicWorker(object arg)
 {
  EmptyBadFixed(arg, 1);
  typeof (BaseClass)
   .GetMethod("EmptyGood",System.Reflection.BindingFlags.Instance |
           System.Reflection.BindingFlags.NonPublic)
   .Invoke(this, new[] {arg, 2});
 }

It looks elegant at first look. But what about performance?
I made the EmptyGood() method protected (normal conditions) during the test and just called the PublicWorker() method 1 000 times. Then I implemented the reflection approach and measured time.

Results are following:
DEBUG BUILD:
normal: 217 ticks;
Reflection: 62773 ticks; 289.28 times slower;

RETAIL BUILD:
normal: 174 ticks;
Reflection: 54971 ticks; 315.93 times slower;

Of course, the consumed time is very little. But we can make some conclusions if we compare the values.


REFLECTION CACHED
-----
Now let's try to somehow optimize the code.
First of all let's make MethodInfo variable static. This will allow to avoid the Reflection from building the object each time.
 private static MethodInfo mi = typeof (BaseClass)
  .GetMethod("EmptyGood", System.Reflection.BindingFlags.Instance |
        System.Reflection.BindingFlags.NonPublic);

 //we need prevent the bad method from calling
 public override void PublicWorker(object arg)
 {
  EmptyBadFixed(arg, 1);
  mi.Invoke(this, new[] {arg, 2});
 }

DEBUG BUILD:
normal: 217 ticks;
Reflection, cached MethodInfo: 52752; 243.1 times slower; delta: -46.18

 RETAIL BUILD:
normal: 174 ticks;
Reflection, cached MethodInfo: 44316; 254.69 times slower; delta: -61.24

We can conclude that we have a significant profit, but still the performance is poor. But anyways, it's better than previous one.


DELEGATES (Native)
----
From the previous test we can notice that delay mostly isn't related to the object fetching, rather it's related to the Invoke() mechanism. It's pretty slow. But is there a way to somehow store the method pointer and call it faster? The answer is YES.

Try to remember what is a delegate? It's the managed pointer to a function. Of course, there are a lot of other supported features such as chains, but basically it stores a pointer to the method. More specifically, it stores a function address and just moves address function to register and executes a call instruction when you invoke it (tested in WinDbg :)). Delegate invocation should be quickly. Le'ts check this.

I again made a EmtpyGood() method protected, but call the delegate (cached, I'll always use the cached object).
 private delegate void GoodDeletage(object arg1, int arg2);

 private GoodDeletage emptyGood;
 public ChildClass()
 {
  emptyGood = new GoodDeletage(EmptyGood);
 }

 public override void PublicWorker(object arg)
 {
  EmptyBadFixed(arg, 1);
  emptyGood(arg, 2);
 }
If we perform the test, we will be able to test whether there is a sense in delegates. And I performed the test:

DEBUG BUILD:
normal: 217 ticks;
delegates: 267 ticks, 1.23 times slower;

RETAIL BUILD:
normal: 174 ticks;
delegates: 204 ticks; 1.17 times slower; PERFECT performance.


DELEGATES (MethodInfo)
----
The results of test are incredible! In retail build delegates invocations are almost equal the direct method calls.
So this is a good way. But there is 2 issues:
  1. What about the protected parts?
  2. The variable isn't static.
So we should find another way on how to create a delegate of a protected method. Fortunately, the Delegate class contains a necessary constructor and we can create delegates based on MethodInfo object:
 
 private Action<object, int> emptyGood;
 public ChildClass()
 {
  var mi = typeof (BaseClass)
   .GetMethod("EmptyGood", System.Reflection.BindingFlags.Instance |
         System.Reflection.BindingFlags.NonPublic);
  emptyGood = (Action<object, int>)Delegate.CreateDelegate(typeof(Action<object, int>),this, mi);
 }

 public override void PublicWorker(object arg)
 {
  EmptyBadFixed(arg, 1);
  emptyGood(arg, 2);
 }
Let's perform the test:

DEBUG BUILD:
normal: 217 ticks;
dyn delegates: 271 ticks; 1.24 times slower than native; delta: +0.01
Reflection, cached MethodInfo: 52752; 243.1 times slower than native;

RETAIL BUILD:
normal: 174 ticks;
dyn delegates: 206 ticks; 1.18 times slower than native; delta: +0.01
Reflection, cached MethodInfo: 44316; 254.69 times slower than native

The results of the invocations of delegates, which are based on MethodInfo object, don't differ from the usual delegates.
These simple actions can allow as to make our unwanted Reflection significantly less painful.
TWO ADDITIONAL LINES OF CODE and method invocation is about 215 times faster. Guys, don't be lazy to implement this :)


-----
LIMITATIONS (it's important)
If you are familiar with alchemy you should know that you can't get anything without giving something instead. Unfortunately, this fact takes a place here too.
The creation of the delegate instance is a bit expensive operation. Lets perform a straightforward test:

public override void PublicWorker(object arg)
        {
            EmptyBadFixed(arg, 1);
            var emptyGood = (Action<object, int>) Delegate.CreateDelegate(
                typeof (Action<object, int>),
                this,
                typeof (BaseClass)
                    .GetMethod("EmptyGood",
                               System.Reflection.BindingFlags.Instance |
                               System.Reflection.BindingFlags.NonPublic));

            emptyGood(arg, 2);
        }

In our test we create the delegate each time we enter the function. This is similar to our first usage of the Reflection, but here we additionally create the Action delegate. The results of the test are below.
DEBUG BUILD:
Reflection: 63392 ticks;
Delegate: 60107 ticks; 1.05 times faster;

RETAIL BUILD:
Reflection: 54971 ticks;
Delegate: 59513 ticks; 1.08 times slower;

In this case the using of delegates gives us a worse performance than using of the Reflection.
So be careful. If your method is static or you're working with single instance of object - this is good idea to use the delegates. But in case you should create one delegate object per a few methods' invocations - there is no sense to do this.


If your method is instance, there are no ways how to make a delegate static. It should be created in the context of the invocation class (we passed this pointer to the delegate constructor).


-----
TIPS
If your method should return value, use the Func<> delegate instead of the Action<> one.


-----
SUMMARIZE:
Old one:

typeof (BaseClass)
 .GetMethod("EmptyGood", 
   System.Reflection.BindingFlags.Instance |
   System.Reflection.BindingFlags.NonPublic));       

New one:
 (Action<object, int>)Delegate.CreateDelegate(
  typeof(Action<object, int>),
  this, 
  typeof (BaseClass)
    .GetMethod("EmptyGood", 
      System.Reflection.BindingFlags.Instance |
      System.Reflection.BindingFlags.NonPublic));


And method invocation works 215 times faster than Invoke(), 1.18 times slower than native call.
But in case on inappropriate usage, 1.08 times slower than Reflection, 342 (R:316) times slower than a native call.

Feel free to leave your objections or remarks.

No comments:

Post a Comment