【问题标题】:MethodInfo.Invoke performance issueMethodInfo.Invoke 性能问题
【发布时间】:2019-09-16 01:03:40
【问题描述】:

我正在从文件中读取和写入数据。文件中的数据可以是浮点数、双精度数、整数等。直到运行时才知道类型。我将存储在文件中的数据类型称为 Tin。从 Tout 类型的数组中读取或写入数据。这种类型也是直到运行时才知道的。

代码序列是这样的。在已知的 Open 方法 Tin 和 Tout 中,我们可以为已知的数据类型创建读写方法。

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

读写循环重复数百万次,并依靠反射调用适当的方法,如下所示。

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

当使用性能分析器检查此代码时,我发现如果时间仅用于调用运行时读写方法,则 c 的数量会很大。

如何加快速度。

【问题讨论】:

    标签: c# .net invoke


    【解决方案1】:

    是的,这是因为反射 API 比直接方法调用慢数千倍。然而,有一些有趣的技术可以解决这个问题。查看 Jon Skeet 在using delegates to cache reflection 上的文章。

    有一个静态设置成本,但是一旦你这样做了,重复调用委托的时间就相当于虚拟方法调用。

    也有一些pre-packaged frameworks 来实现同样的事情。

    【讨论】:

    • +1 用于缓存代理。装箱和拆箱也很慢;你不应该把(Tout[]) 放在一个循环中。我想编译器可能会对此进行优化,但常识告诉我们要把所有可能的事情都放在一个长循环之外。
    • 装箱/拆箱 我注意到如果您在包含数百万个元素的紧密循环中执行此操作,会导致性能下降约 10 倍!所以是的,当然,如果可以通用输入上述内容,请这样做
    • 这个答案对我自己的问题帮助很大:stackoverflow.com/questions/17418304/…
    【解决方案2】:

    这将为您做任何事情,几乎与直接调用一样快。

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

    【讨论】:

    • Compile 调用开销放在一边,在“直接”方法调用之上仍然有以下开销:arguments 数组分配/收集的成本,参数转换的成本 (两次),实例转换的成本,以及多个附加方法调用的成本(FastMethodInfo.InvokeDelegate.Invoke 和可能的 VoidDelegate.Invoke - 尽管,FastMethodInfo.Invoke 可能会被编译器内联)。您在此处的内容看起来可以很好地替代 MethodInfo.Invoke,但“几乎与直接通话一样快”这句话非常具有误导性。
    • 我不同意。我给出了完全正确的想法或印象,尤其是与替代方法相比。快速创建概念验证项目并亲眼看看。我认为您要说的是我不够精确或不够彻底,但鉴于 OP 的问题是:“我如何加快速度?”
    • 哦,在解决问题中提出的要求方面,我绝不会批评代码的质量。如果您说“它比反射快得多”,我不会争论-因为确实如此。如果您说“在某些情况下它几乎与直接调用一样快”,我什至不会争论——因为确实如此。但我可以想到一些可行的用例,其中 FastMethodInfo 比直接方法调用慢很多倍,这导致我强烈反对“几乎与直接调用一样快”部分的措辞。
    • 我在 .net 4.7 下测试了 Expression 解决方案,它的运行速度与 MethodInfo.Invoke 一样快(有时甚至比它慢)。我应该说“干得好,微软”吗? ^_^
    • 您的实现甚至比原始反射调用还要慢。请在dotnetfiddle.net/vHFjhh 尝试委托反射方法。这是从@KirillShlenskiy 克隆的
    【解决方案3】:

    配置文件以找到符合您期望的解决方案:

    .Net Framework 提供了大量方法来动态调用方法。然而,它们在性能方面的表现并不相同,而且它们也不是同样易于使用。

    CreateDelegate 可能就是你要找的东西

    在 .Net Framework 的最新版本中,CreateDelegate 比 MethodInfo 调用高出 50 倍:

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

    查看post of mine 以查看不同方法调用的基准

    【讨论】:

      猜你喜欢
      • 2017-04-13
      • 2022-01-04
      • 1970-01-01
      • 2010-09-05
      • 2012-09-25
      • 2015-05-28
      • 2015-06-20
      • 2018-09-27
      相关资源
      最近更新 更多