21

I am reading and writing data to and from a file. The data in the file can be floats, doubles, ints etc. The type is not known until runtime. I will refer to data type stored in the file as Tin. Data is read into or written from arrays of type Tout. This type too is not known until runtime.

The code sequence is something like this. In the Open method Tin and Tout are known, we can create read and write methods for the known data types.

Open(...)
{
   MethodInfo ReadMethod = typeof(...)GetMethod("ReadGeneric").MakeGenericMethod(new Type[] {typeof(Tin), typeof(Tout)}));
}

The read write loops repeat millions of times and rely on reflection to invoke the appropriate methods as shown below.

Read loop
{
   var values = (Tout[])ReadMethod.Invoke(this,new object[]{index});
   process ...
}

When examining this code using a performance profiler I find that c collosal amount if time is spent just invoking the runtime read write methods.

How do I speed this up.

svick
  • 236,525
  • 50
  • 385
  • 514
Basil Furdas
  • 211
  • 1
  • 2
  • 3

3 Answers3

31

Yes, this is due to the fact that the reflection API is thousands of times slower than direct method calls. There are some interesting techniques to work around this however. Check out Jon Skeet's article on using delegates to cache reflection.

There is a static setup cost but once you have done that the time to invoke the delegate repeatedly is equivalent to virtual method calls.

There are also some pre-packaged frameworks to achieve the same thing.

Dr. Andrew Burnett-Thompson
  • 20,980
  • 8
  • 88
  • 178
  • 1
    +1 for caching delegates. What's also slow is boxing and unboxing; you shouldn't put `(Tout[])` in a loop. I suppose it's possible that the compiler could optimize this but common sense says to keep everything possible outside of a long loop. – Jamie Treworgy Apr 25 '12 at 10:46
  • Boxing/Unboxing I noticed if you do this in a tight loop with millions of elements incurs a performance hit of around 10x! So yes definitely, if its possible to generically type the above, do it – Dr. Andrew Burnett-Thompson Apr 25 '12 at 10:56
  • 1
    This answer helped me a lot with my own question: http://stackoverflow.com/questions/17418304/cannot-bind-to-the-target-method-when-creating-delegates-for-properties/ – Alex Hope O'Connor Jul 03 '13 at 05:47
  • The "using delegates to cache reflection" link is dead. Is there another page that shows the same thing? – Noah Heber Jun 08 '23 at 22:54
10

This'll do anything for ya, almost as fast as a direct call.

using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

public class FastMethodInfo
{
    private delegate object ReturnValueDelegate(object instance, object[] arguments);
    private delegate void VoidDelegate(object instance, object[] arguments);

    public FastMethodInfo(MethodInfo methodInfo)
    {
        var instanceExpression = Expression.Parameter(typeof(object), "instance");
        var argumentsExpression = Expression.Parameter(typeof(object[]), "arguments");
        var argumentExpressions = new List<Expression>();
        var parameterInfos = methodInfo.GetParameters();
        for (var i = 0; i < parameterInfos.Length; ++i)
        {
            var parameterInfo = parameterInfos[i];
            argumentExpressions.Add(Expression.Convert(Expression.ArrayIndex(argumentsExpression, Expression.Constant(i)), parameterInfo.ParameterType));
        }
        var callExpression = Expression.Call(!methodInfo.IsStatic ? Expression.Convert(instanceExpression, methodInfo.ReflectedType) : null, methodInfo, argumentExpressions);
        if (callExpression.Type == typeof(void))
        {
            var voidDelegate = Expression.Lambda<VoidDelegate>(callExpression, instanceExpression, argumentsExpression).Compile();
            Delegate = (instance, arguments) => { voidDelegate(instance, arguments); return null; };
        }
        else
            Delegate = Expression.Lambda<ReturnValueDelegate>(Expression.Convert(callExpression, typeof(object)), instanceExpression, argumentsExpression).Compile();
    }

    private ReturnValueDelegate Delegate { get; }

    public object Invoke(object instance, params object[] arguments)
    {
        return Delegate(instance, arguments);
    }
}
Daniel Henry
  • 856
  • 7
  • 18
  • 1
    Putting the `Compile` call overhead to the side, you still have the following overheads on top of the "direct" method call: cost of `arguments` array allocation/collection, cost of argument casting (twice), cost of instance casting, and the cost of multiple additional method invocations (`FastMethodInfo.Invoke`, `Delegate.Invoke` and potentially `VoidDelegate.Invoke` - although, granted, `FastMethodInfo.Invoke` will probably be inlined by the compiler). What you have here looks like a great substitute for `MethodInfo.Invoke`, but the phrase "almost as fast as a direct call" is very misleading. – Kirill Shlenskiy Jan 07 '17 at 01:48
  • I disagree. I'm giving exactly the right idea or impression, especially in comparison to the alternative approaches. Whip up a quick proof of concept project and see for yourself. I think what you are trying to say is that I've been imprecise or not thorough, but I would contest even that criticism given that the OP's question was: "How do I speed this up?" – Daniel Henry Jan 08 '17 at 02:44
  • Oh, and I am in no way criticising the quality of the code with respect to addressing the requirements laid out in the question. I wouldn't argue if you said "it's massively faster than reflection" - because it is. I wouldn't even argue if you said "in some scenarios it is almost as fast as a direct call" - because it is. But I can think of a number of viable use cases where FastMethodInfo is many times slower than a direct method call, leading me to strongly object to the wording of the "almost as fast as a direct call" part. – Kirill Shlenskiy Jan 08 '17 at 04:38
  • I tested the Expression solution under .net 4.7, and it run as fast as (sometimes even slower than) MethodInfo.Invoke. Should I say "Well done, Microsoft" ? ^_^ – horeaper Nov 19 '17 at 08:06
  • 1
    Your implementation even slower than original reflection invocation. Please try delegate reflection approach here https://dotnetfiddle.net/vHFjhh. This is cloned from @KirillShlenskiy – Nguyen Minh Hien Dec 17 '18 at 15:06
  • Oh, for sure. But that's assuming you know the signature of the method at compile time. The OP stated that this will not be known until runtime. – Daniel Henry Dec 17 '18 at 15:45
  • Thank you @Daniel for this solution. Adopted it in the Unity 3D game I'm building and I didn't get any noticeable performance hits (calls were made each frame for several objects) - but I did cache `FastMethodInfo` instances. Didn't use `GetType().GetMethod("...").Invoke(...)` as it explicitly requires the object instance for first parameter, which I supplied that in `FastMethodInfo`'s constructor. CreateDelegate isn't working in Unity very well so `FastMethodInfo` is definitely my best option since I'm working with generics. Thanks! – user1865775 Mar 25 '22 at 09:29
2

Profile to find the solution that matches your expectations :

.Net Framework offers plenty of methods to invoke dynamically methods. However they don't perform equally in terms of performance and they are not equally easy to use.

CreateDelegate may be what you're looking for

In the recent versions of .Net Framework, CreateDelegate beat by a factor 50 the MethodInfo invoke:

// The following should be done once since this does some reflection
var method = typeof (...).GetMethod("ReadGeneric");
// Here we create a Func that targets the instance of type which has the 
// ReadGeneric method
var func = (Func<Tin, Tout[]>)_method.CreateDelegate(typeof(Func<Tin, Tout[]>), target);

// Func will be 50x faster than MethodInfo.Invoke
// use func as a standard Func like 
// var tout = func(index);

Check this post of mine to see benchmark on different method invocations

Community
  • 1
  • 1
Fab
  • 14,327
  • 5
  • 49
  • 68