Showing posts with label Reflection. Show all posts
Showing posts with label Reflection. Show all posts

Saturday, November 26, 2016

10x faster than Delegate.DynamicInvoke

This is a follow up to my previous blog posts, Optimizing Dynamic Method Invokes in .NET, and Dynamically Invoke Methods Quickly, with InvokeHelpers.EfficientInvoke. Basically, I have re-implemented this for Tact.NET in a way that makes it smaller, faster, and compatible with the .NET Standard.

So, how much faster is this new way of doing things? EfficientInvoker.Invoke is over 10x faster than Delegate.DynamicInvoke, and 10x faster than MethodInfo.Invoke.

Check out the source on GitHub:

Simple Explanation

Here is an example of a method and a class that we might want to invoke dynamically...

public class Tester
{
    public bool AreEqual(int a, int b)
    {
        return a == b;
    }
}

...and then here is the code that the EfficientInvoker will generate at runtime to call that method:

public static object GeneratedFunction(object target, object[] args)
{
    return (object)((Tester)target).AreEqual((int)args[0], (int)args[1]);
}

See, it's simple!

Sunday, August 21, 2016

Dynamically Invoke Methods Quickly, with InvokeHelpers.EfficientInvoke

In my previous blog post, I talked about Optimizing Dynamic Method Invokes in .NET. In this post, we will use that information to create a static helper method that is twice as fast as MethodInfo.Invoke.

Basically, we create and cache a delegate in a concurrent dictionary, and then cast both it and it's arguments to dynamics and invoke them directly. The concurrent dictionary introduces overhead, but it still more than twice as fast as calling MethodInfo.Invoke. Please note that this method is highly optimized to reduce the use of hash code look ups, property getters, closure allocations, and if checks.

let's take a look at the code...

InvokeHelpers.EfficientInvoke

public static class InvokeHelpers
{
    private const string TooManyArgsMessage = "Invokes for more than 10 args are not yet implemented";
 
    private static readonly Type VoidType = typeof(void);
 
    private static readonly ConcurrentDictionary<Tuple<string, object>, DelegatePair> DelegateMap 
        = new ConcurrentDictionary<Tuple<string, object>, DelegatePair>();
 
    public static object EfficientInvoke(object obj, string methodName, params object[] args)
    {
        var key = Tuple.Create(methodName, obj);
        var delPair = DelegateMap.GetOrAdd(key, CreateDelegate);
            
        if (delPair.HasReturnValue)
        {
            switch (delPair.ArgumentCount)
            {
                case 0: return delPair.Delegate();
                case 1: return delPair.Delegate((dynamic)args[0]);
                case 2: return delPair.Delegate((dynamic)args[0], (dynamic)args[1]);
                case 3: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2]);
                case 4: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3]);
                case 5: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4]);
                case 6: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5]);
                case 7: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6]);
                case 8: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7]);
                case 9: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7], (dynamic)args[8]);
                case 10: return delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7], (dynamic)args[8], (dynamic)args[9]);
                default: throw new NotImplementedException(TooManyArgsMessage);
            }
        }
 
        switch (delPair.ArgumentCount)
        {
            case 0: delPair.Delegate(); break;
            case 1: delPair.Delegate((dynamic)args[0]); break;
            case 2: delPair.Delegate((dynamic)args[0], (dynamic)args[1]); break;
            case 3: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2]); break;
            case 4: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3]); break;
            case 5: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4]); break;
            case 6: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5]); break;
            case 7: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6]); break;
            case 8: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7]); break;
            case 9: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7], (dynamic)args[8]); break;
            case 10: delPair.Delegate((dynamic)args[0], (dynamic)args[1], (dynamic)args[2], (dynamic)args[3], (dynamic)args[4], (dynamic)args[5], (dynamic)args[6], (dynamic)args[7], (dynamic)args[8], (dynamic)args[9]); break;
            default: throw new NotImplementedException(TooManyArgsMessage);
        }
 
        return null;
    }
 
    private static DelegatePair CreateDelegate(Tuple<string, object> key)
    {
        var method = key.Item2
            .GetType()
            .GetMethod(key.Item1);
 
        var argTypes = method
            .GetParameters()
            .Select(p => p.ParameterType)
            .Concat(new[] { method.ReturnType })
            .ToArray();
 
        var newDelType = Expression.GetDelegateType(argTypes);
        var newDel = Delegate.CreateDelegate(newDelType, key.Item2, method);
 
        return new DelegatePair(newDel, argTypes.Length - 1, method.ReturnType != VoidType);
    }
 
    private class DelegatePair
    {
        public DelegatePair(dynamic del, int argumentCount, bool hasReturnValue)
        {
            Delegate = del;
            ArgumentCount = argumentCount;
            HasReturnValue = hasReturnValue;
        }
 
        public readonly dynamic Delegate;
        public readonly int ArgumentCount;
        public readonly bool HasReturnValue;
    }
}

Now let's take a look at some performance tests...

Optimizing Dynamic Method Invokes in .NET

I recently had a lot of fun helping to optimize some RPC code that was using reflection to dynamically invoke methods in a C# application. Below are a list of implementations that we experimented with, and their performance.

  1. Directly Invoking the Method
  2. Using MethodInfo.Invoke
  3. Using Delegate.DynamicInvoke
  4. Casting to a Func
  5. Casting a Delegate to Dynamic

Spoilers: Here are the results. (The tests for this can be see below.)

Name First Call (Ticks) Next Million Calls Invoke Comparison
Invoke 1 39795 -
MethodInfo.Invoke 12 967523 x24
Delegate.DynamicInvoke 32 1580086 x39
Func Invoke 731 41331 x1
Dynamic Cast 1126896 85495 x2

Conclusion: Invoking a method or delegate directly is always fastest, but when you need to execute code dynamically, then (after the first invoke) the dynamic invoke of a delegate is significantly faster than using reflection.

Let's take a look at the test code...

Wednesday, April 30, 2014

How to make a Private Method in to a Public Method in .NET

Disclaimer: I actually recommend that you try to use this technique as little as possible.

Once and a while we all have to work with a poorly designed API, and sometimes you just really need to access to a private method inside of their code. So when you are out of other options, what can you do to access a private method?

You can try to decompile the code and fork it or extend it, but that might not work due to type constraints, and even if it does then you have to maintain multiple versions. The most common thing to do is use reflection to access the private methods or members, but then you have to share that ugly reflection code everywhere.

Just make an extension method.

Use reflection, but expose it as a extension method. This gives the illusion that the method you are exposing is natively public. This solution is simple and reusable, but please do not abuse it!

Real Time Web Analytics