【问题标题】:Are static methods more efficient?静态方法更有效吗?
【发布时间】:2011-01-09 16:02:04
【问题描述】:

在内存和时间方面,将方法设为静态会更好吗?

【问题讨论】:

  • 回答你的问题:是的,但你永远不应该考虑到这一点。请参阅 LBushkin 的回答。
  • 完全改写了问题。听起来您指的是使方法静态化的实际过程。 ;) 重构工具使这个过程更快!

标签: c# performance static-methods


【解决方案1】:

这类问题有很多。静态方法更快/更慢吗?虚拟功能更快/更慢吗? i++ 是否比 ++i 快/慢? for(;;) 比 while(true) 快/慢吗?

值得拥有一些软件和tuning it。这将使您对实际影响软件性能的事物有一个很好的认识。

然后您会发现,此类问题的答案(大多数情况下)是微不足道的。

如果我可以概括的话,根据我的经验,使软件变慢的原因是使用了看似无害的代码行,但其时间消耗可能比想象的要大几个数量级。由于它们看起来很无辜,因此无法通过查看代码可靠地找到它们。示例:重复分配、初始化和释放大型数据结构只是为了确保它们存在。国际化不需要的字符串。通知风格的编程,可以将一个简单的属性设置转变为整个大型数据结构中的大量方法调用。简单的 O(n^2) 操作,除非 n 变大,否则永远不会成为问题。认为 (a

【讨论】:

    【解决方案2】:

    静态方法和非虚拟实例方法之间几乎没有区别。后者只是将this prointer/reference 作为“隐藏”参数。在生成的机器代码中,两种调用看起来非常相似。

    如果方法不依赖/修改对象,则它应该是静态的。

    另一方面,虚拟方法(可覆盖)要求调用者在所谓的vtable 中查找确切的实现。除了防止编译器内联非常小的方法(简单的属性访问器经常内联)之外,查找还需要几个周期。

    不过,虚方法是 C#/CLR 上最快的一种动态调度。比委托和反射快得多。

    【讨论】:

      【解决方案3】:

      我发现我在给定类中编写的许多实用程序方法只需要类中的几个数据成员,或者根本不需要。在这些情况下,我倾向于将这些方法编写为(独立的)静态方法,直接传入它们需要的少数数据项。

      如果它们对其他类特别通用且足够有用,我也可以将它们公开。

      【讨论】:

        【解决方案4】:

        通常是的,不需要传递“this”引用。该引用在 ECX 寄存器中传递,因此不需要额外的堆栈空间。如果您从同一类的实例方法进行调用,则该寄存器已经设置,根本不会节省任何费用。但是当方法在另一个类中时,它可以帮助减轻x86 CPU内核的压力,x86没有很多寄存器。看到可衡量的性能改进是极其罕见的。

        我确实将不使用实例成员的类的方法标记为静态的。我看重静态关键字提供的内在契约:“此方法不会改变对象状态。”

        【讨论】:

        • 最后一段+1——这才是真正重要的,它使static变得无价。
        • 现代 x86 cpus 使用寄存器别名。
        • 我同意这个答案,尤其是关于对象状态合同。在我的代码中,如果一个方法可以标记为静态,那么我会标记它,只是为了更容易推理程序的行为。当然,静态(非对象)状态仍然可以发生变异,这在某种程度上会使分析复杂化。我通过始终锁定静态突变来解决这个问题。这并不能直接帮助分析,但它确实鼓励我重新思考设计并摆脱状态突变!
        • 对我来说,您的回答非常合乎逻辑,但我看到很多人称静态为纯代码气味。
        • 逻辑永远胜过舆论。
        【解决方案5】:

        如果方法不需要来自其所属类的任何状态信息,则应将其设为静态。

        如果您不关心多态性,您可以将任何方法编写为实例或静态,只需决定是否采用类实例成员并将它们作为参数传递给方法。 你应该考虑的,是语法是否自然,代码是否易于理解和有意义等等。

        您可能不必担心在此级别进行优化,因为实例与静态方法的性能开销可以忽略不计。是的,调度表中使用了一些空间用于类型(如果方法是虚拟的) - 但这是一个很小的、恒定的开销。是的,与静态方法相比,调用实例方法的开销也很小 - 但同样很小。

        这似乎是某种程度的微优化,除非您有可衡量的、有形的证据相信确实会影响程序性能,否则您应该避免这样做。事实上,如果你做错了,传递额外参数(将它们复制到堆栈等)而不是通过你的类型的隐藏 this 引用访问它们的成本可能会导致更糟 性能。

        你最好分析方法的语义,并在此基础上做出静态/实例决策。

        【讨论】:

        • 据我了解,Microsoft C# 生成的 IL 代码对所有实例方法使用虚拟调度 (callvirt),无论它是否是虚拟的,即使对于密封类也是如此。这是使用 callvirt 的内置空值检查。否则,必须对空值检查进行编码,因为 CLR 在空实例上调用实例方法没有问题。
        • 是的,但理论上,抖动在某些情况下可以内联非虚拟方法。在密封类等情况下,它还可以做一些静态分析来替换callvirt。我不确定它实际上在多大程度上做到了这一点,但这是可能的。
        • 引用伟大的 Jeff Atwood - 你可以用任何语言编写 Fortran。
        • @Jeffrey,callvirt 必须用于跨程序集边界的调用,因为将方法设为虚拟是一项非破坏性更改(而将其设为虚拟->非虚拟将是)。由于虚拟方法在 CLI 中是显式的(而不是在 JVM 中是隐式的),即使在非优化模式下,JIT 也可以确定是使用直接调度还是间接调度。唯一的开销是空值检查,这在当前处理器上几乎是免费的。
        • @Mathias:是的,静态方法本身很容易测试,但是您不能覆盖它们并用存根替换它们。例如,如果您的静态方法访问数据库并且您正在测试该类,则您不能覆盖它并让它返回一个预填充的集合。但这些只是细节,不同的测试策略会处理这种情况。我想我只是在“不喜欢静态方法”阵营中。
        【解决方案6】:

        在大多数情况下差异可以忽略不计,但静态更有效。

        在静态方法调用中避免了以下步骤:

        1. 检查对象引用 (this) 是否为空。
        2. 在虚拟调度表上找到正确的方法。
        3. 将对象引用放在堆栈上。

        【讨论】:

          【解决方案7】:

          如果你还是要传递实例 (SomeStaticMethod(obj, "abc", 123);),那不是真的。您只能在没有多态性的场景中有效地使用静态方法,在这种情况下,任何简单的东西很可能无论如何都会被内联。

          “自然地”使用对象 (obj.SomeMethod("abc",123);) - 保持代码简单,profile 发现性能问题 - 非常不太可能在实例和静态,除非您正在运行一些 非常 紧密的循环。 一些场景可能很重要,但它们非常专业。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-06-14
            • 1970-01-01
            • 2012-07-14
            • 2011-01-27
            • 2015-08-06
            • 2013-02-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多