【问题标题】:How slow is Reflection反射有多慢
【发布时间】:2010-10-20 18:26:20
【问题描述】:

我最近创建了一个接口层来区分 DataAccessProvider 和我们的业务逻辑层。 使用这种方法,我们可以随时通过更改 Web/App.Config 中的值来更改对 DataAccessProvider 的选择。 (如果需要可以提供更多细节)。

无论如何,为此我们使用反射来完成我们可以工作的 DataProvider 类。

/// <summary>
/// The constructor will create a new provider with the use of reflection.
/// If the assembly could not be loaded an AssemblyNotFoundException will be thrown.
/// </summary>
public DataAccessProviderFactory()
{
    string providerName = ConfigurationManager.AppSettings["DataProvider"];
    string providerFactoryName = ConfigurationManager.AppSettings["DataProviderFactory"];
    try
    {
        activeProvider = Assembly.Load(providerName);
        activeDataProviderFactory = (IDataProviderFactory)activeProvider.CreateInstance(providerFactoryName);
    }
    catch
    {
        throw new AssemblyNotFoundException();
    }
}

但现在我想知道反射有多慢?

【问题讨论】:

标签: c# performance reflection assemblies


【解决方案1】:

在大多数情况下:足够快。例如,如果您使用它来创建 DAL 包装器对象,与连接到网络所需的时间相比,通过反射创建对象所花费的时间将微不足道。所以优化这将是浪费时间。

如果您在紧密循环中使用反射,有一些技巧可以改进它:

  • 泛型(使用包装器 where T : new()MakeGenericType
  • Delegate.CreateDelegate(对类型化委托;不适用于构造函数)
  • Reflection.Emit - 铁杆
  • Expression(类似于Delegate.CreateDelegate,但更灵活,适用于构造函数)

但出于您的目的,CreateInstance 非常好。坚持下去,让事情变得简单。


编辑:虽然关于相对性能的观点仍然存在,虽然最重要的事情“衡量它”仍然存在,但我应该澄清上述一些内容。有时……它确实很重要。先测量。然而,如果你发现它太慢了,你可能想看看像FastMember这样的东西,它在后台安静地执行所有Reflection.Emit代码,给你一个很好的简单API;例如:

var accessor = TypeAccessor.Create(type);
List<object> results = new List<object>();
foreach(var row in rows) {
    object obj = accessor.CreateNew();
    foreach(var col in cols) {
        accessor[obj, col.Name] = col.Value;
    }
    results.Add(obj);
}

这很简单,但会非常快。在我提到的具体示例中,我提到了一个 DAL 包装器——如果你正在做很多事情,请考虑像 dapper 这样的东西,它再次在后台执行所有 Reflection.Emit 代码,为你提供最快但易于使用的 API:

int id = 12345;
var orders = connection.Query<Order>(
    "select top 10 * from Orders where CustomerId = @id order by Id desc",
    new { id }).ToList();

【讨论】:

  • 如果有人想看看反射发射是如何访问字段的(它不是太复杂)请参阅:sharpanalytics.blogspot.de/2012/08/…
  • @Marc:我一直在使用反射来获取方法,当前方法的类名来记录 try-catch 中的错误。基本上是为了避免在记录错误时对函数名称进行硬编码。我需要担心吗?
  • @Sangram 可能不是,不
【解决方案2】:

与非反射代码相比,它的速度较慢。重要的不是它是否缓慢,而是它是否缓慢它很重要。例如,如果您在 Web 环境中使用反射来实例化对象,而预期的并发性可能会上升到 10K,那么它会很慢。

不管怎样,不用提前关心性能是好的。如果事情变得很慢,如果您设计正确,您总是可以加快速度,以便将您预期将来可能需要优化的部分本地化。

如果你需要加快速度,你可以查看这篇著名的文章:

Dynamic... But Fast: The Tale of Three Monkeys, A Wolf and the DynamicMethod and ILGenerator Classes

【讨论】:

    【解决方案3】:

    以下是一些可能有帮助的链接:

    【讨论】:

    • 不要感到震惊。一百万次迭代的最长时间测量为 22 秒。在最坏的情况下,每次调用需要 22 秒。除非您要创建大量此类对象,否则这真的没什么大不了的。当然,如果您正在创建大量这些对象,那么这可能是一件大事,但正如 Marc 指出的那样,它仍然会被数据库连接和查询时间所淹没。除非您知道它对性能至关重要,否则不要被“x 倍慢”的文章吓到。
    • 我同意,尽管速度较慢,但​​对于大多数应用程序而言,性能损失不会超过使用反射的好处。
    【解决方案4】:

    我想我应该做一个快速测试来证明与没有反射相比有多慢。

    带反射

    • 通过迭代每个属性并匹配来实例化 58 个对象
    • 总时间:52254 纳秒

      while (reader.Read()) {
          string[] columns = reader.CurrentRecord;
          CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry();
          IEnumerable<PropertyInfo> rawPayFileAttributes = typeof(CdsRawPayfileEntry).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(CustomIndexAttribute)));
          foreach (var property in rawPayFileAttributes) {
              int propertyIndex = ((CustomIndexAttribute)property.GetCustomAttribute(typeof(CustomIndexAttribute))).Index;
              if (propertyIndex < columns.Length)
                  property.SetValue(toReturn, columns[propertyIndex]);
              else
                  break;
          }
      }
      

    无反射

    • 通过创建新对象实例化 58 个对象
    • 总时间:868 纳秒

          while (reader2.Read()) {
              string[] columns = reader2.CurrentRecord;
              CdsRawPayfileEntry toAdd = new CdsRawPayfileEntry() {
                  ColumnZero = columns[0],
                  ColumnOne = columns[1],
                  ColumnTwo = columns[2],
                  ColumnThree = columns[3],
                  ColumnFour = columns[4],
                  ColumnFive = columns[5],
                  ColumnSix = columns[6],
                  ColumnSeven = columns[7],
                  ColumnEight = columns[8],
                  ColumnNine = columns[9],
                  ColumnTen = columns[10],
                  ColumnEleven = columns[11],
                  ColumnTwelve = columns[12],
                  ColumnThirteen = columns[13],
                  ColumnFourteen = columns[14],
                  ColumnFifteen = columns[15],
                  ColumnSixteen = columns[16],
                  ColumnSeventeen = columns[17]
              };
          }
      

    虽然不完全公平,因为反射还必须在通过反射创建新对象的基础上检索每个属性的特定属性 58*18 次,但它至少提供了一些视角。

    【讨论】:

      【解决方案5】:

      反射并没有那么慢。通过反射调用方法比普通方法慢大约 3 倍。如果您只执行一次或在非关键情况下执行此操作,那没问题。如果您在时间紧迫的方法中使用它 10000 次,我会考虑更改实现。

      【讨论】:

      • 如果这是真的,我真的很喜欢这个说法。 “通过反射调用方法比正常方式慢大约 3 倍。”你有参考吗?
      • 如果我的帖子已经发布了大约 3 年,我不记得我是从哪里得到这些信息的。
      • 我将数据访问层从 FieldInfo 和 PropertyInfo GetValue SetValue 转换为编译后的表达式。读取 30,000 行 40 列所需的时间从 4 秒缩短到 1 秒。但是,在并排比较中,反射在设置和获取值时比编译表达式慢 200 到 250 倍。
      【解决方案6】:

      除了遵循其他答案中给出的链接并确保您没有编写“病态糟糕”的代码之外,对我来说,最好的答案是自己测试。

      只有您知道瓶颈在哪里,您的反射代码将被用户使用多少次,反射代码是否会处于紧密循环中等。您知道您的业务案例,有多少用户将访问您的网站,性能如何要求是。

      但是,鉴于您在此处显示的代码的 sn-p,那么我的猜测是反射的开销不会是一个大问题。

      VS.NET Web 测试和性能测试功能应该使测量此代码的性能变得非常简单。

      如果您不使用反射,您的代码会是什么样子?它会有什么限制?如果您删除反射代码,您可能无法忍受自己发现的限制。可能值得尝试在没有反射的情况下设计此代码,以查看它是否可能或替代方案是否可取。

      【讨论】:

        【解决方案7】:

        在我开始玩 IoC 之前,我一直在做类似的事情。我会使用 Spring 对象定义来指定数据提供者 - SQL、XML 或 Mocks!

        【讨论】:

        • Spring.net 非常有能力在运行时更新依赖项。如果您更新配置文件并从工厂重新加载实例,您将获得对更新后实例的引用。 (请注意,如果您从 app.config 加载配置,则仅当您使用单独的 spring XML 文件时,这不起作用。
        猜你喜欢
        • 1970-01-01
        • 2010-11-26
        • 2015-12-23
        • 2018-09-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-20
        • 2014-04-04
        相关资源
        最近更新 更多