【问题标题】:What are the performance implications of marking methods / properties as virtual?将方法/属性标记为虚拟对性能有何影响?
【发布时间】:2010-10-06 13:24:43
【问题描述】:

问题如标题所述:将方法/属性标记为虚拟对性能有何影响?

注意 - 我假设虚拟方法在常见情况下不会被重载;我通常会在这里使用基类。

【问题讨论】:

    标签: c# performance virtual


    【解决方案1】:

    与直接调用相比,虚拟函数的性能开销很小。在低级别,您基本上是在查看数组查找以获取函数指针,然后通过函数指针进行调用。现代 CPU 甚至可以在其分支预测器中相当好地预测间接函数调用,因此它们通常不会对现代 CPU 管道造成太大伤害。在汇编级别,虚函数调用转换为类似以下内容,其中I 是任意立即值。

    MOV EAX, [EBP + I] ; Move pointer to class instance into register
    MOV EBX, [EAX] ;  Move vtbl pointer into register.
    CALL [EBX + I]  ;   Call function
    

    对比。以下是直接函数调用:

    CALL I  ;  Call function directly
    

    真正的开销在于虚拟函数在大多数情况下不能内联。 (如果 VM 意识到它们总是会去同一个地址,它们可以是 JIT 语言。)除了内联本身获得的加速之外,内联还支持其他一些优化,例如常量折叠,因为调用者可以知道被调用者如何在内部工作。对于大到不能被内联的函数,对性能的影响可能可以忽略不计。对于可能被内联的非常小的函数,这时您需要注意虚函数。

    编辑:要记住的另一件事是所有程序都需要流量控制,而这从来都不是免费的。什么会取代你的虚拟功能?一个switch语句?一系列 if 语句?这些仍然是可能无法预测的分支。此外,给定一个 N 路分支,一系列 if 语句将在 O(N) 中找到正确的路径,而虚函数将在 O(1) 中找到它。 switch 语句可能是 O(N) 或 O(1),具体取决于它是否优化为跳转表。

    【讨论】:

    • 我在一段时间内看到的信息量最大的帖子之一,我之前并不了解,但仍然很容易理解。
    • 很好的答案 - 非常感谢。因为我不打算在一个非常紧凑的循环中调用我的方法,所以我不会太担心这个开销。谢谢你的帮助=)
    • Anton Ertl 维护了一个有用的基准页面(带有源代码),其中包括各种处理器的直接和间接函数调度成本:complang.tuwien.ac.at/forth/threading
    • 还要注意 switch 语句可以使用二分查找来实现,在这种情况下,它的复杂度是 O(log N) 而不是 O(N):stackoverflow.com/questions/2596320/…
    【解决方案2】:

    Rico Mariani 在他的 Performance Tidbits blog 中概述了有关性能的问题,他在其中指出:

    虚拟方法:你在使用 直接调用时的虚拟方法 会做?很多时候人们一起去 允许未来的虚拟方法 可扩展性。可扩展性是一个 好东西,但它确实是有代价的 – 确保您的完全可扩展性 故事已经制定,你的使用 虚函数实际上是在进行 把你带到你需要去的地方。 例如,有时人们认为 通过呼叫站点问题,但随后 不要考虑如何“扩展” 对象将被创建。 后来他们意识到(大部分) 虚函数根本没有帮助 他们需要一个完全不同的 模型以获取“扩展”对象 进入系统。

    密封:密封可以是一种 限制你的多态性 上课只是那些网站 需要多态性。如果你会 完全控制类型然后密封 对性能来说可能是一件好事 因为它支持直接呼叫和 内联。

    基本上反对虚方法的论点是它不允许代码成为内联的候选者,而不是直接调用。

    在MSDN文章Improving .NET Application Performance and Scalability中对此做了进一步的阐述:

    考虑虚拟会员的权衡

    使用虚拟成员提供可扩展性。如果您不需要扩展您的课程 设计,避免使用虚拟成员,因为虚拟成员调用成本更高 表查找,它们会破坏某些运行时性能优化。例如,编译器不能内联虚拟成员。此外,当您允许子类型化时,您实际上向消费者提供了一个非常复杂的合同,并且当您将来尝试升级您的类时,您不可避免地会遇到版本控制问题。

    然而,对上述内容的批评来自 TDD/BDD 阵营(他们希望方法默认为虚拟),他们认为无论如何性能影响都可以忽略不计,尤其是当我们可以访问速度更快的机器时。

    【讨论】:

      【解决方案3】:

      通常,虚拟方法只需通过一个函数指针表即可到达实际方法。这意味着一次额外的取消引用和一次内存往返。

      虽然成本并非绝对为零,但成本极低。 如果它对你的程序有虚拟功能有帮助,那么就去做吧。

      最好有一个设计良好的程序,它的性能影响很小,很小,很小,而不是为了避免 v-table 而设计一个笨拙的程序。

      【讨论】:

      • 虚函数调用的最大代价不是从 vtable 加载指针,而是由于错误预测的分支(并且 vjump 通常被错误预测)导致的管道清除。这可以与管道本身一样长。对于非常频繁调用的函数,它会累加。
      • @Crashworks:有趣的想法。然而,这似乎与其他问题 (stackoverflow.com/questions/10757167/…) 中的一些 cmets 相矛盾。愿意发表评论吗? (我不知道谁是对的……只是想尽我所能吸收所有信息)
      • 该问题中的大多数答案都是错误的,尤其是对于有序处理器,因为通常会错误预测间接分支。
      • 现在我想知道这是否是特定于 C# 的,因为这违背了所有一般 C++ 性能知识
      • @Crashworks 完全正确,并不是我们有间接性,而是缓存未命中导致性能变差
      【解决方案4】:

      很难确定,因为 .NET JIT 编译器可能能够在某些(很多?)情况下优化开销。

      但如果它不优化掉它,我们基本上是在谈论额外的指针间接。

      也就是说,当你调用一个非虚方法时,你必须

      1. 保存寄存器,生成函数 prologue/epilogue 以设置参数,复制返回值等。
      2. 跳转到一个固定的、静态已知的地址

      1 在这两种情况下都是相同的。至于 2,使用虚拟方法,您必须改为从对象的 vtable 中的固定偏移量读取,然后跳转到该点的任何位置。这使得分支预测更加困难,并且可能会将一些数据推出 CPU 缓存。所以差异不是很大,但如果你让每个函数调用都是虚拟的,它可以加起来。

      它还可以抑制优化。编译器可以轻松地内联对非虚拟函数的调用,因为它确切地知道调用了哪个函数。使用虚函数,这有点棘手。一旦确定调用了哪个函数,JIT 编译器可能仍然能够执行此操作,但工作量要大得多。

      总而言之,它仍然可以加起来,尤其是在性能关键领域。但除非每秒至少调用几十万次函数,否则您无需担心。

      【讨论】:

        【解决方案5】:

        从你的标签,你说的是 c#。我只能从德尔福的角度回答。我认为这将是相似的。 (我在这里期待负面反馈:))

        静态方法将在编译时链接。虚拟方法需要在运行时进行查找以决定调用哪个方法,因此开销很小。只有当方法很小并且经常被调用时,它才有意义。

        【讨论】:

          【解决方案6】:

          I ran this test in C++。虚函数调用(在 3ghz PowerPC 上)比直接函数调用要长 7-20 纳秒。这意味着它只对您计划每秒调用一百万次的函数,或者对于那些很小以至于开销可能大于函数本身的函数才重要。 (例如,出于盲目的习惯将访问器函数设为虚拟可能是不明智的。)

          我还没有在 C# 中运行我的测试,但我希望那里的差异会更小,因为 CLR 中的几乎每个操作都涉及间接操作。

          【讨论】:

          • “解释运行时”?啊cmon人,.Net甚至不是真正的虚拟机,9年后仍然有人认为.Net被解释了....pff
          • 不,您会发现您计划调用数百万次的函数只会对性能造成一点影响。当您更改调用的函数并因此创建缓存未命中时,您将在哪里找到命中
          【解决方案7】:

          在桌面端,方法是否重载无关紧要,它们会通过方法指针表(虚拟方法表)产生额外的间接级别,这意味着在方法之前通过间接读取大约 2 个额外的内存call 比较了非密封类和非最终方法上的非虚方法。

          [有趣的事实是,在紧凑型框架 1.0 版上,过热更大,因为它不使用虚拟方法表,而只是通过反射来发现调用虚拟方法时要执行的正确方法。]

          与非虚拟方法相比,虚拟方法不太可能成为内联或其他优化(如尾调用)的候选对象。

          这大概是方法调用的性能层次:

          非虚方法

          但是,除非您通过 测量 来证明,否则各种调度机制的这些性能影响都无关紧要;)(即便如此,架构影响、可读性等可能对哪一个有很大影响选择)

          【讨论】:

            猜你喜欢
            • 2011-06-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-09-05
            • 1970-01-01
            • 2019-09-04
            • 2016-12-19
            相关资源
            最近更新 更多