【问题标题】:.NET reflection 3 times slower in .NET 4.7.x than in 3.5.NET 4.7.x 中的 .NET 反射比 3.5 慢 3 倍
【发布时间】:2020-01-23 04:44:15
【问题描述】:

我有一个从 .NET 3.5 升级到 .NET 4.7.2 的应用程序。唯一的问题是我们使用反射的部分代码的性能。我上传到 Gist 的一个简单示例可以很好地解释整个情况:https://gist.github.com/vkocjancic/3e8a6b3496c412a75b1c85a1d2ba1111

基本上,我们有一个 POCO 类,如果未为不可为空类型的属性设置值,则其属性方法会抛出异常。

[编辑]:

  1. 是的,我知道这不正确或不适合使用,但是,该应用程序是在 .NET 1.1 中启动的。

  2. 是的,它应该早就修复了。不是。

然后使用反射来获取 POCO 类实例的属性名称和值,并将其填充到 DataTable。

示例和实际项目源代码完全相同。唯一的区别是,在一种情况下它是使用 .NET 3.5 编译的,而在第二种情况下是使用 .NET 4.7.2 编译的。

这是 10 次调用所经过的平均时间(以毫秒为单位):

.NET 3.5      ->  231.1 ms
.NET 4.7.2    ->  713.5 ms
.NET Core 2.2 -> 1013.2 ms

谁能详细说明为什么 .NET 4.7.2 中的反射比 .NET 3.5 慢 3 倍,以及如何解决这个问题。只有在未设置属性并引发异常时才会发生滞后。如果填充属性值,则性能没有差异。

如果我在调试器中运行示例,.NET 3.5 构建永远不会触发 MissingFieldException。使用 .NET 4.7.2 构建会触发每个异常。

[编辑]

正如@bevan 提到的,在切换到 .NET 4.0 时实际上会出现减速。 > 此外,问题归结为在 .NET 3.5 中引发的异常和处理速度比在 .NET 4.0 中快 3 倍

【问题讨论】:

  • 我同意并且我真的很愿意。然而,这意味着改变我们 90% 的域 POCO 类。如果可能,宁愿避免这种情况。还。它仍然不能解释 .NET 3.5 和 4.7.1 之间的 3 倍差异
  • 随机的想法,但是看看这里的第一个例子,这里使用 FastMember 和 ObjectReader.Create / table.Load... github.com/mgravell/fast-member#ever-needed-an-idatareader - 有什么用吗?它做了很多工作让你在反射时不那么糟糕(它还有可选的参数来定义哪些属性和以什么顺序读取)
  • @VladimirKocjancic 很公平,但我的观点是:FastMember 存在 提供一些机制来避免反射最慢的部分;该示例只是一种用法,特定于DataTable - 但也有用于许多其他目的的 API。通常,如果性能是一个因素,您将需要使用一些更复杂的元编程 API——通常是“表达式”或“引用发射”;这些都是困难; FastMember 提供了一个可访问的表面 - 不如完整的元编程,但比原始反射更好。
  • 多次运行基准测试,我发现了两个值得注意的细节。首先,当您在 .NET 3.5 和 4.0 之间切换目标时会出现减速 - .NET 4.0 的更高版本的差异可以忽略不计。其次,注释掉 throw 语句会导致 .NET 4.0 的运行速度提高一倍左右,因此您看到的性能差异是由于 异常抛出/处理 的差异而不是反射。
  • 嗯,这个假设当然同样有缺陷。 .NET 4.0 对 .NET 2.0 来说并不是一个小的增量改进,它在底层有很大的不同。确保更改不会降低异常处理的速度几乎肯定不是目标。对于绝大多数应用程序,使用 4.0 运行时而不是 2.0 运行时不会倒退,实际上会提高运行时间。您的应用程序是一个不寻常的情况。您的选择是修复您的应用程序,坚持使用 3.5,使用 .NET Core 和/或 open an issue 试试运气。

标签: c# exception reflection


【解决方案1】:

正如我在 cmets 中提到的,如果您在访问属性之前调用 IsPROP_NUMERIC_ANull 函数,它不会再抛出任何异常并且速度非常快。

foreach (var myObject in objects)
{
    var row = table.NewRow();

    // TODO implement some caching: dont call GetProperties(), GetMethod(), for every object
    var type = myObject.GetType();
    var properties = type.GetProperties();

    foreach (var info in properties)
    {
        try
        {
            // call the IsNullMethod for the property, eg "IsPROP_NUMERIC_ANull"
            var isNullMethodName = $"Is{info.Name}Null";
            var isNullMethod = type.GetMethod(isNullMethodName, BindingFlags.Instance | BindingFlags.Public);

            if (isNullMethod != null)
            {
                var fldIsNull = (bool)isNullMethod.Invoke(myObject, new object[] { });
                if (!fldIsNull)
                    row[info.Name] = info.GetValue(myObject, null);
            } else
            {
                // isNullMethod doesn't exist
                row[info.Name] = info.GetValue(myObject, null);
            }
        }
        catch
        {
            // do nothing
        }
    }
    table.Rows.Add(row);
}

总运行时间为 3 ms 与之前的 19500 毫秒相比。

如果你可以重新设计它,我会使用像 Marc Gravell proposed 这样的缓存代表。

【讨论】:

  • 我想到了,我已经实现了。问题是有 20% 的属性会抛出没有 IsNull 方法的异常,因为,好吧,我能说什么,遗留代码。
  • @MickyD... 我赞成这个答案。 :) 提个醒。对于为什么 .NET 4.7.2 执行与 .NET 3.5 相同的任务所需的时间是 .NET 3.5 的 3 倍,仍然没有任何回应:)
  • @VladimirKocjancic 干得好,先生。我也想知道为什么会这样:)
猜你喜欢
  • 2020-07-07
  • 2019-05-06
  • 2010-10-05
  • 2021-11-15
  • 2020-06-04
  • 2012-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多