【问题标题】:Speeding up Reflection Invoke C#/.NET加速反射调用 C#/.NET
【发布时间】:2017-10-27 08:03:55
【问题描述】:

有很多关于加速反射调用的帖子,示例如下:

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

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

这里:

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



我的问题是关于加快泛型调用。这可能吗?

我有一个抽象类和一个实现它的类...

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++; }
}

我要做的是建立这些消息处理程序类的列表并快速调用它们的 HandleMessage()


目前,我正在做的事情大概是这样的:

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());

这不是全部,但它是重要的东西......

method.Invoke 非常慢,我想保留类中的泛型参数,我意识到我可以将其锁定为对象并将其强制转换为 HandleMessage 方法,但我试图避免这样做.

有什么办法可以加快速度吗?它目前比直接调用慢几个数量级。

任何帮助将不胜感激。

【问题讨论】:

    标签: c# .net reflection invoke


    【解决方案1】:

    使用Delegate.CreateDelegate() 应该会快很多。你最终会得到一个指向真正函数的指针,而不是调用 Invoke() 的委托。

    试试这个:

    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 });
    

    在同一个类中添加以下泛型方法。我们在上面动态调用它是因为我们在编译时不知道类型参数。

    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);
    }
    

    然后你有一个委托将参数转换为所需的类型,然后调用HandleMessage 方法。

    【讨论】:

    • 打败我,但你应该使用MethodInfo 重载来更接近地匹配他的问题。
    • 如果你看我的回答,你会发现他的问题比这个复杂一点(因为泛型参数)。
    • 谢谢乔纳森。我可以看到问题所在。我已经编辑了我的以显示另一个不涉及表达式的解决方案。希望那个有效。
    • var paramType = handlerType.GetGenericArguments()[0]; 这一行应该是var paramType = method.GetGenericArguments()[0]; ?@TimRogers
    【解决方案2】:

    您使用的是 C# 4 吗?如果是这样,dynamic 可能会加快速度:

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

    【讨论】:

    • 这似乎抛出了 RuntimeBinderException:匹配“X”的最佳重载方法有一些无效参数。
    • @Rob:((dynamic)handler).HandleMessage((dynamic)message) 呢?
    • 是的,它比直接调用快得多,但比 Invoke() 快得多。谢谢你。我只是在评估其他人发布的内容。
    • 我已经用这个了,它非常简洁,易于维护,而且速度快,可能不如其他方法快,但足够快。感谢所有发布的人,尤其是 Jonathan 和 Gabe。
    • 很好的答案,谢谢!我刚刚使用过这种方法,就我而言,它和直接调用一样快。
    【解决方案3】:

    您可以使用Delegate::CreateDelegate。这比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()); 
    

    随意对其进行基准测试,但我之前已经对其进行了基准测试,并且速度明显更快。

    编辑:我现在看到了你的问题,你不能按照我建议的方式去做。

    您可以使用表达式编译为您执行调用的委托,这将非常快:

    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());
    

    编辑:除了我上面的表达式示例之外,以下内容也可以使用 - 这也是我在这些类型的场景中所做的。

    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!");
        }
    }
    

    【讨论】:

    • 这似乎引发了 ArgumentException : Error binding to target method。对象似乎没有绑定到通用参数。
    • 这似乎失败了: var lambda = Expression.Lambda>(ifExpr, instExpr, paramExpr); ---- 'MyMessageType' 类型的 ParameterExpression 不能用于 'Message' 类型的委托参数---- 这是您的第一次编辑 btw。
    • @Rob - 在这里工作正常(如果你使用我提供的类);您的课程示例与您在代码中使用的示例完全相同吗?您还修改了var paramExpr 行吗?看起来你无论如何都有答案 - 但如果你逐字复制它确实有效。
    • 同意,这是一个有效的解决方案。
    • 您的最后一个解决方案使我免于在运行时创建泛型方法,然后调用或委托。谢谢!
    【解决方案4】:

    如果您知道签名,请使用Delegate.CreateDelegate

    如果您不知道签名,那么要获得快速的东西是非常棘手的。如果您需要速度,那么无论您做什么,都尽量避免Delegate.DynamicInvoke 非常慢。

    (请注意,这里的“慢”是相对而言的。请确保您确实需要对此进行优化。DynamicInvoke 相当于每秒 250 万次调用(在我的机器上),这很可能足够快。下面的实现更像是每秒 110+ 百万次调用,并且比 Method.Invoke 更快。)

    我发现an article 讨论了一种方法(在编译时不知道签名的情况下快速调用方法)。这是我的实现版本。奇怪的问题是您可以构建一个表示调用的 lambda,但您不会知道该 lambda 的签名,并且必须动态(缓慢地)调用它。但是,您可以将强类型调用烘焙到 lambda 中,其中 lambda 表示调用行为而不是特定方法本身。 (lambda 最终成为 Func&lt;object, object[], object&gt;,您在其中传递一个对象和一些值并取回返回值。)

    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;
    }
    

    【讨论】:

      【解决方案5】:

      不,那(很遗憾)不可能。反射 很慢,MethodInfo.Invoke() 也不例外。您不能使用(通用)接口并因此直接调用吗?

      编辑更新:要真正加快速度,我想到了一件事,但编码开销很大:您可以使用动态代码生成和编译。这将意味着动态构建无需反射即可调用该方法的源代码,动态编译和执行它。这意味着对创建和编译执行您的工作的类的初始性能影响,但随后您可以直接调用每个后续调用。

      【讨论】:

      • 为什么编码开销“巨大”?我希望只有几行代码使用Expression
      • 首先,当使用表达式(或 CodeDom)时,它的可维护性很繁琐。调试/验证生成的代码也更难了。最好动态生成 C# 代码并编译它。这使您可以更好地控制生成的控件。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-10
      • 1970-01-01
      • 2011-11-30
      相关资源
      最近更新 更多