开发者

Speeding up Reflection Invoke C#/.NET

开发者 https://www.devze.com 2023-03-30 10:48 出处:网络
There are plenty of posts on speeding up reflection invokes, examples here: Speeding up Reflection API with delegate in开发者_如何学Go .NET/C#

There are plenty of posts on speeding up reflection invokes, examples here:

Speeding up Reflection API with delegate in开发者_如何学Go .NET/C#

https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/

and here:

Example : Speeding up Reflection API with delegate in .NET/C#



My question is about speeding up generic invokes. Is this possible at all?

I've got an abstract class and a class which implements it...

public abstract class EncasulatedMessageHandler<T> where T : Message
{
    public abstract void HandleMessage(T message);
}

public class Handler : EncasulatedMessageHandler<MyMessageType>
{
    public int blat = 0;
    public override void HandleMessage(MyMessageType message) { blat++; }
}

What I want to do is build up a list of these message handler classes and quickly invoke their HandleMessage()


At the moment, I'm doing something that's approximately this:

object handler = Activator.CreateInstance(typeof(Handler)); // Ignore this, this is done up front.

MethodInfo method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);

Action<object> hook = new Action<object>(delegate(object message)
{
    method.Invoke(handler, new object[] { message });
});

// Then when I want to invoke it:

hook(new MyMessageType());

That's not the whole thing, but it's the important stuff...

The method.Invoke is very slow, I'd like to keep the generic parameters on the class, I realise I could lock this down to object and cast it in the HandleMessage method, but I'm trying to avoid doing this.

Is there anything I can do to speed this up? It's currently orders of magnitude slower than direct calls.

Any help would be appreciated.


Are you using C# 4? If so, dynamic may speed things up:

Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);


Using Delegate.CreateDelegate() should be a lot faster. You will end up with a pointer to the real function, not a delegate that calls Invoke().

Try this:

object handler = Activator.CreateInstance(typeof(Handler)); 
var handlerType = handler.GetType();
var method = handlerType.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var paramType = handlerType.GetGenericArguments()[0];

// invoke the MakeHandleMessageDelegate method dynamically with paramType as the type parameter
// NB we're only doing this once
Action<object> hook = (Action<object>) this.GetType().GetMethod("MakeHandleMessageDelegate")
            .MakeGenericMethod(paramType)
            .Invoke(null, new [] { handler });

In the same class add the following generic method. We invoke this dynamically above because we don't know the type parameter at compile time.

public static Action<object> MakeHandleMessageDelegate<T>(object target)
{
    var d = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), target, "HandleMessage");

    // wrap the delegate another that simply casts the object parameter to the required type
    return param => d((T)param);
}

You then have a delegate that casts the parameter to the required type, then calls the HandleMessage method.


You can use Delegate::CreateDelegate. This is significantly faster than Invoke().

var handler = Activator.CreateInstance(typeof(Handler));
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var hook = (Action<object>)Delegate.CreateDelegate(typeof(Action<object>), handler, method);

// Then when you want to invoke it: 
hook(new MyMessageType()); 

Feel free to benchmark it, but I have benched it before and it was significantly faster.

Edit: I see your problem now, you can't do it the way I suggested.

You can use Expressions to compile a delegate that does the invoke for you, this will be very fast:

var type = typeof(Handler);
var instance = Activator.CreateInstance(type);
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);

var originalType = type;
// Loop until we hit the type we want.
while (!(type.IsGenericType) || type.GetGenericTypeDefinition() != typeof(EncasulatedMessageHandler<>))
{
    type = type.BaseType;
    if(type == null)
        throw new ArgumentOutOfRangeException("type");
}

var messageType = type.GetGenericArguments()[0]; // MyMessageType

// Use expression to create a method we can.
var instExpr = Expression.Parameter(typeof(object), "instance");
var paramExpr = Expression.Parameter(typeof(Message), "message");
// (Handler)instance;
var instCastExpr = Expression.Convert(instExpr, originalType);
// (MyMessageType)message
var castExpr = Expression.Convert(paramExpr, messageType); 
// ((Handler)inst).HandleMessage((MyMessageType)message)
var invokeExpr = Expression.Call(instCastExpr, method, castExpr); 
// if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message);
var ifExpr = Expression.IfThen(Expression.TypeIs(paramExpr, messageType), invokeExpr);

// (inst, message) = { if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message); }
var lambda = Expression.Lambda<Action<object, Message>>(ifExpr, instExpr, paramExpr);
var compiled = lambda.Compile();
Action<Message> hook = x => compiled(instance, x);

hook(new MyMessageType());

Edit: Apart from my Expression example above, the following will also work - and is what I do in these types of scenarios.

var instance = (IEncapsulatedMessageHandler)Activator.CreateInstance(typeof(Handler));
instance.HandleMessage(new MyMessageType());

public class Message { }

public class MyMessageType : Message { }

public interface IEncapsulatedMessageHandler
{
    void HandleMessage(Message message);
}

public abstract class EncasulatedMessageHandler<T> : IEncapsulatedMessageHandler where T : Message
{
    public abstract void HandleMessage(T message);

    void IEncapsulatedMessageHandler.HandleMessage(Message message)
    {
        var msg = message as T;
        if (msg != null)
            HandleMessage(msg);
    }
}

public class Handler : EncasulatedMessageHandler<MyMessageType>
{
    public override void HandleMessage(MyMessageType message)
    {
        Console.WriteLine("Yo!");
    }
}


If you know the signature, use Delegate.CreateDelegate.

If you don't know the signature, it's very tricky to get something that's fast. If you need speed, then whatever you do, try to avoid Delegate.DynamicInvoke which is extremely slow.

(Note that "slow" is very relative here. Make sure you really need to optimize this. DynamicInvoke is something like 2.5 million invocations per second (on my machine), which is very likely fast enough. The implementation below is more like 110+ million invocations per second and is faster than Method.Invoke.)

I found an article that discusses a way to do it (invoke a method fast without knowing the signature at compile time). Here is my version of the implementation. The weird issue is that you could build a lambda which represents the invocation, but you would not know the signature of that lambda and would have to call it dynamically (slowly). But instead, you can bake the strongly typed invocation into the lambda, with the lambda representing the act of invocation rather than the specific method itself. (The lambda ends up being a Func<object, object[], object> where you pass an object and some values and get back the return value.)

public static Func<object, object[], object> ToFastLambdaInvocationWithCache(
   this MethodInfo pMethodInfo
) {
   Func<object, object[], object> cached;
   if (sLambdaExpressionsByMethodInfoCache.TryGetValue(pMethodInfo, out cached))
      return cached;

   var instanceParameterExpression = Expression.Parameter(typeof(object), "instance");
   var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args");

   var index = 0;
   var argumentExtractionExpressions =
      pMethodInfo
      .GetParameters()
      .Select(parameter =>
         Expression.Convert(
            Expression.ArrayAccess(
               argumentsParameterExpression,
               Expression.Constant(index++)
            ),
            parameter.ParameterType
         )
      ).ToList();

   var callExpression = pMethodInfo.IsStatic
      ? Expression.Call(pMethodInfo, argumentExtractionExpressions)
      : Expression.Call(
         Expression.Convert(
            instanceParameterExpression, 
            pMethodInfo.DeclaringType
         ),
         pMethodInfo,
         argumentExtractionExpressions
      );

   var endLabel = Expression.Label(typeof(object));
   var finalExpression = pMethodInfo.ReturnType == typeof(void)
      ? (Expression)Expression.Block(
           callExpression,
           Expression.Return(endLabel, Expression.Constant(null)), 
           Expression.Label(endLabel, Expression.Constant(null))
        )
      : Expression.Convert(callExpression, typeof(object));

   var lambdaExpression = Expression.Lambda<Func<object, object[], object>>(
      finalExpression,
      instanceParameterExpression,
      argumentsParameterExpression
   );
   var compiledLambda = lambdaExpression.Compile();
   sLambdaExpressionsByMethodInfoCache.AddOrReplace(pMethodInfo, compiledLambda);
   return compiledLambda;
}


No, that is (sadly) not possible. Reflection is slow and MethodInfo.Invoke() is no exception. Couldn't you use (generic) Interfaces and thus direct calls?

Edit update: One thing comes to mind to really speed this up, but the coding overhead is massive: You can use dynamic code generation and compilation. That would mean dynamically building the source code that would call the method without reflection, dynamically compiling and executing this. This would mean an initially performance impact for creating and compiling the classes that do your work, but then you have direct calls for every subsequent calls.

0

精彩评论

暂无评论...
验证码 换一张
取 消