【问题标题】:If reflection is inefficient, when is it most appropriate?如果反射效率低下,什么时候最合适?
【发布时间】:2010-07-31 09:40:30
【问题描述】:

我发现很多情况下我自己认为可以使用反射来解决问题,但我通常不会,因为我听到很多类似“不要使用反射,它太低效”的说法.

现在我遇到了一个问题,除了在new T() 中使用反射之外,我找不到任何其他解决方案,如this question & answer 中所述。

所以我想知道是否有人可以告诉我反射的具体预期用途,以及是否有一套指导方针来指示何时合适,何时不合适?

【问题讨论】:

    标签: c# reflection


    【解决方案1】:

    代码效率的一个有用抽象是将其划分为三类时间,每类相隔大约 3 个数量级。

    首先是人类时间。当您只需要让一个人对您的代码的性能感到满意时,您可以做很多事情。人类无法感知需要 10 毫秒或 20 毫秒的代码之间的区别,两者看起来都是即时的。当一个程序需要 6 秒而不是 5 秒,大约 3 0 亿 条机器指令时,人类是宽容的。以人工方式运行的程序的常见示例是编译器和点击式设计器。使用反射从来都不是问题。

    然后是 I/O 时间。当您的程序需要访问磁盘或网络时。 I/O 很慢,在磁盘的情况下受到机械运动的限制,在网络的情况下受到带宽和延迟的限制。您总是可以判断 I/O 何时成为瓶颈,您的程序正在运行,但并没有过多地增加 CPU 负载。操作系统一直在阻塞线程,让它一直等到 I/O 请求完成。

    反射在 I/O 时运行。要检索类型数据,CLR 必须读取程序集元数据。如果以前没有这样做,您的程序将导致页面错误,要求操作系统从磁盘读取数据。接下来是,粗略地说,反射可以使 I/O 绑定代码只慢两倍。通常更好,因为在第一次 perf 命中后,元数据被缓存并且可以更快地检索。因此,反射通常是一种可接受的折衷方案。典型的例子是序列化和 dbase ORM。

    然后是机器时间。 CPU内核的原始性能是惊人的。一个属性 getter 可以在 0 到 1/2 纳秒之间的某处执行纳秒。这确实与PropertyInfo.GetValue() 相比较。两者都会让 CPU 保持忙碌,您会看到核心的 CPU 负载为 100%。但是 GetValue() 花费数百甚至数千条机器代码指令。不计算在元数据中分页所需的时间。虽然增量时间不多,但在您循环时会快速累积。

    如果您无法将反射代码分类为人工时间或 I/O 时间类别,则反射不太可能成为常规代码的合适替代品。

    【讨论】:

    • "如果以前没有这样做,您的程序将导致页面错误" 这是否意味着运行 GetType().GetMethod(" prefix_" + userInputString).Invoke()Activator.CreateInstance(Type.GetType(userInputString))MethodBase.GetCurrentMethod() 只会减慢第一次在每个方法中调用它,但在所有后续调用中它不会效率低下?这是我通常使用它的目的,如果效率低下仅限于应用程序启动后调用它的前几次,我可以肯定地忍受它。
    • 漂亮!当你试图让 CPU 燃烧时,不要因为任何需要它自己检查的东西(反射的真正含义)而陷入困境。
    • @nl-x:第二次和后续使用可能比第一次使用快 100 倍......但仍然比直接访问慢一千倍。 (嗯,JIT 基础设施中的所有代码在第一次使用时都非常慢,因为那是 JIT 编译器运行的时候)。相比之下,正如我的回答所暗示的,调用通过反射创建的委托仅比直接调用慢 3 倍。因此,如果您要多次调用,请考虑缓存,如果超过 5 次,请考虑缓存需求。
    【解决方案2】:

    防止反射减慢程序速度的关键是不要在循环中使用它。如果要在启动期间从对象读取属性(仅发生一次),请使用反射。您想从 10,000 个未知类型的对象的列表中读取一个属性,使用反射获取一次属性 getter 委托(搜索词:PropertyInfo.GetGetMethod),然后调用 10,000 个类型的委托。 StackOverflow 上有很多这样的例子。

    【讨论】:

    • 获取代表拳头并在循环时调用它的非常有用的提示。
    【解决方案3】:

    它通常“足够快”,如果您需要更快(对于紧密循环等),您可以使用 ExpressionILGenerator(可能通过 DynamicMethod)进行元编程,以使 非常 快速的代码(包括一些你在 C# 中做不到的技巧)。

    反射更通常用于框架/库场景,其中库根据定义对调用者一无所知,并且必须基于配置、属性或模式工作。

    【讨论】:

    • 在 .NET 4.0 上,还有 dynamic 的选项,它具有呼叫站点缓存等优化。所以在反射和元编程之间有一个中间步骤。事实上,我尽可能使用dynamic,而不是直接使用反射,只是为了可读性。
    • +1 用于 ILGenerator 和 DynamicMethod;它们可能很有趣,并且非常适合反射太慢的罕见情况(通常是因为您想在内部循环中重复使用它。)它们可能会让人上瘾,但使用 Emit 的代码更难维护(有些我的同事们看着我使用 Emit 编写的代码,并将其视为某种巫术魔法。)
    • @Dan - 你想在 protobuf-net "v2" 中看到一些粘稠的东西;元编程in extremis ;p
    【解决方案4】:

    如果有一件事我讨厌听到它是“不要使用反射,它的效率太低了”。

    效率太低了怎么办?如果您正在编写一个每月运行一次且时间要求不高的控制台应用程序,因为您使用了反射,因此需要 30 秒而不是 28 秒真的很重要吗?

    什么时候不适合使用的指南只有你才能真正组合起来,因为它们在很大程度上取决于你在做什么以及替代品的效率/性能如何。

    【讨论】:

    • 我喜欢你正在抢劫的观点,我一直在听到它,但我确信我听到它的人是从其他人那里听到的,而这些人又是从其他人那里听到的。 ....这就是问题的重点。我想自己知道是对还是错
    • @DaveDev,它本身从来没有对错之分,这就是你使用它的方式。有点像“枪不杀人,人杀人”的说法。
    • 当你需要它的时候,或者当其他解决方案太复杂的时候。
    • 现在它也意味着“不要使用 IoC”和“不要使用单元测试框架”。
    • @DaveDev 是的,人们对反射可能出现的性能问题感到偏执。这些人通常对语言或编译器的内部工作没有太多了解。
    【解决方案5】:

    反思并非低效。它比直接调用效率低。因此,当没有等效的编译时安全方法时,我个人使用反射。恕我直言,反射的问题不在于效率,而在于代码的脆弱性,因为它使用了对重构非常不友好的魔术字符串。

    【讨论】:

      【解决方案6】:

      我将它用于插件架构 - 在插件文件夹中查看程序集以查找标有自定义属性的方法,该属性指示有关插件的信息 - 并在日志框架中。框架检测程序集本身的自定义属性,该属性包含有关程序集作者、项目、版本信息和其他标记的信息,这些信息与堆栈跟踪中的所有内容一起记录。

      打算泄露一个“商业机密”,但这是一个很好的。该框架允许您使用“Story ref”标记每个方法或类,例如

      [StoryRef(Ref="ImportCSV1")]

      ...想法是它会集成到我们的敏捷项目管理框架中:如果在该类/方法中抛出任何异常,日志记录方法将使用反射来检查堆栈跟踪中的 StoryRef 属性,如果是这样,那将被记录为该故事的例外。在 PM 软件中,您可以通过 Story 看到异常(故事就像一个极端/敏捷的用例)。

      我认为这是一个有效的用途,至少!基本上,当它看起来是最整洁适当的方式时,我会使用反射。没有其他任何东西真正涉及到它 - 我想不出你会使用反射来进行如此多的调用来提高效率。

      【讨论】:

        【解决方案7】:

        所以我想知道是否有人可以告诉 我反思的具体目的 用法,如果有一组 指示何时发生的指南 合适,什么时候不合适?

        反射的一个示例是from Wikipedia

        //Without reflection
        Foo foo = new Foo();
        foo.Hello();
        
        //With reflection
        Type t = Type.GetType("FooNamespace.Foo");
        object foo = Activator.CreateInstance(t);
        t.InvokeMember("Hello", BindingFlags.InvokeMethod, null, foo, null);
        

        在这里,使用反射没有任何优势:不使用反射的代码不仅效率更高,而且更易于理解。

        良好的反射的用途是诸如序列化和对象关系映射之类的东西,如果您有一个类的属性列表,它们很容易实现,但否则需要为每个类自定义编写的函数.

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-11-27
          • 2022-01-21
          • 1970-01-01
          • 1970-01-01
          • 2011-09-13
          • 1970-01-01
          • 2019-06-21
          • 2013-03-17
          相关资源
          最近更新 更多