静态方法与实例方法的性能


108

我的问题与静态方法与实例方法的性能特征及其可伸缩性有关。对于这种情况,假设所有类定义都在单个程序集中,并且需要多个离散的指针类型。

考虑:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

上面的类表示助手样式模式。

在实例类中,解决实例方法要花一些时间与StaticClass相对。

我的问题是:

  1. 当不关心保持状态(不需要字段或属性)时,使用静态类总是更好吗?

  2. 如果存在大量此类静态类定义(例如,假设有100个静态方法,每个静态方法有多个),那么与相同数量的实例类定义相比,这会对执行性能或内存消耗产生负面影响吗?

  3. 当调用同一实例类中的另一个方法时,实例解析是否仍然发生?例如使用[此]关键字等this.DoOperation2("abc")从内DoOperation1相同的实例的。



“实例解析”是什么意思?在IL级别,“ this”指针与任何其他局部变量一样可用。实际上,在某些旧版本的CLR / JIT上,您可以在NULL上调用实例方法,前提是该方法没有碰到“ this”(代码刚刚通过并且没有崩溃。)。现在CLR / JIT包含显式null-检查每个成员调用..
quetzalcoatl

> vijaymukhi.com/documents/books/ilbook/chap8.htm和“调用实例”而不是“调用”。前者期望'this'参数,而后者则期望。
quetzalcoatl 2012年

@Quetzalcoatl对此感到困惑,问题是来自同一实例的更多方法到另一方法的问题,如果这需要将实例解析为自身。
伯尼·怀特

1
@quetzalcoatl我以为他的意思是:“ this当类本身调用实例方法时,编译器会摆脱检查指向某物的问题吗?”
乔恩·汉娜

Answers:


152

从理论上讲,由于其他隐藏this参数,静态方法应该比实例方法稍微好一些,而其他所有条件都相同。

实际上,这几乎没有什么区别,以致于它会被各种编译器决策所掩盖。(因此,两个人可以“证明”一个人比另一个人更好,但结果不一致)。尤其重要的this是,通常在寄存器中传递,并且通常从该寄存器开始。

最后一点意味着,从理论上讲,我们应该期待一个静态方法,该方法将一个对象作为参数并对其执行某些操作,其效果比该对象作为实例的等效方法稍差。同样,差异很小,以至于如果您尝试测量它,最终可能会测量其他一些编译器决策。(特别是因为该引用始终在寄存器中的可能性也很高)。

真正的性能差异将归结为您是人为地将对象存储在内存中以执行应自然为静态的操作,还是以复杂的方式缠结对象传递链来执行应为实例的操作。

因此对于数字1。当不关心保持状态时,最好总是保持静态,因为这就是static的含义。这不是性能问题,尽管总的原则是要很好地进行编译器优化-比起那些有特殊用途的案例,有人更有可能去优化那些有常规用途的案例。

2号。没有区别。对于每个成员来说,每类都有一定数量的成本,这取决于元数据的数量,实际DLL或EXE文件中的代码量以及实际的代码量。无论是实例还是静态,这都是相同的。

随着第3项,thisthis做。但是请注意:

  1. this参数在一个特定的寄存器传递。当在同一类中调用实例方法时,它很可能已经在该寄存器中(除非由于某种原因它被隐藏并使用了该寄存器),因此无需采取任何操作this即可将设置为需要设置的值。这在一定程度上适用于例如方法的前两个参数,该方法是它进行的调用的前两个参数。

  2. 由于很明显它this不为null,因此在某些情况下可用于优化调用。

  3. 既然很明显this不为null,这可能会使内联方法调用更加有效,因为为伪造该方法调用而生成的代码可以忽略某些可能仍然需要的null检查。

  4. 也就是说,空检查很便宜!

值得注意的是,作用于对象的通用静态方法(而不是实例方法)可以减少在http://joeduffyblog.com/2011/10/23/on-generics-and-some-of-在没有为给定类型调用给定静态变量的情况下,the-associated-heads /。正如他所说:“顺便说一句,事实证明,扩展方法是使泛型抽象更具按需付费的好方法。”

但是,请注意,这仅与该方法使用的其他类型的实例化有关,否则将不存在。因此,它实际上不适用于许多情况(某些其他实例方法使用该类型,其他地方的其他代码使用该类型)。

摘要:

  1. 通常情况下,实例与静态的性能成本可以忽略不计。
  2. 例如,如果您滥用静电,反之亦然,通常会有什么成本。如果您不将其作为静态和实例之间决策的一部分,则更有可能获得正确的结果。
  3. 在极少数情况下,另一种类型的静态泛型方法导致创建的类型比实例泛型方法少,这会使它有时变得很少使用(很少使用)(并且“很少”指的是它在类型中与哪种类型一起使用)。应用程序的生命周期,而不是调用频率)。一旦您了解了他在该文章中所讨论的内容,您就会发现它与大多数static-vs-instance决策无关。编辑:而且它大多数只用ngen花费,而不用jitted代码。

编辑:关于便宜的空值检查的注释(我在上面声明过)。.NET中的大多数null检查根本不检查null,而是在假设它可以工作的情况下继续执行原打算,并且如果发生访问异常,它将变成NullReferenceException。这样,大多数情况下,当C#代码在概念上涉及空检查(因为它正在访问实例成员)时,如果成功,则成本实际上为零。某些内联调用是个例外,(因为它们想要表现得像调用实例成员一样),并且它们只是命中字段以触发相同的行为,因此它们也很便宜,而且无论如何仍然经常被忽略掉(例如,如果方法的第一步涉及按原样访问字段)。


您能否评论一下静态vs实例问题是否对缓存一致性有影响?对一个或另一个的依赖更可能导致高速缓存未命中吗?是否有一个很好的大纲说明原因?
scriptocalypse

@scriptocalypse不是。指令缓存不会有任何区别,在该级别上,通过this或通过显式参数访问数据之间没有太大区别。更大的影响将是数据与相关数据的接近程度(值类型字段或数组值比引用类型字段中的数据更近)和访问方式。
乔恩·汉娜

“从理论上讲,我们应该期望以对象为参数并对其执行某些操作的静态方法要比在同一对象上作为实例的等效方法稍微好一些。”-您是否表示如果上述示例方法采用参数作为对象而不是字符串,非静态更好吗?例如:我有我的静态方法将对象作为参数并将其序列化为字符串并返回字符串。您是否建议在这种情况下使用非静态?
batmaci

1
@batmaci我的意思是很有可能obj.DoSomehting(2)会比便宜一些,DoSomething(obj, 2)但正如我也说的那样,差异是如此之小,并且依赖于最终在最终编译中可能会有所不同的微小事物,因此根本就不值得担心。如果您执行的操作与将某些内容序列化为字符串一样昂贵(相对于此处的演奏差异),那么它就毫无意义。
乔恩·汉纳

在这个本来很好的答案中,有一件事可能是显而易见的,但很重要:一个实例方法需要一个实例,而创建一个实例并不便宜。即使是默认值,也ctor需要初始化所有字段。一旦有了实例,该答案就会适用(“所有其他条件都相等”)。当然,昂贵的cctor方法也会使静态方法变慢,但这只是在第一次调用时才同样适用于实例方法。又见docs.microsoft.com/en-us/previous-versions/dotnet/articles/...
阿贝尔

8

当不关心保持状态(不需要字段或属性)时,使用静态类总是更好吗?

我会说,是的。在声明某些内容时,static您声明了无状态执行的意图(这不是强制性的,而是一种人们期望的意图)

如果存在大量此类静态类(例如,有100个静态类,每个静态类都有多个静态方法),那么与相同数量的实例类相比,这会对执行性能或内存消耗产生负面影响吗?

别这么认为,除非您确定静态类确实是无节制的,否则,很容易弄乱内存分配并导致内存泄漏。

当[this]关键字用于调用同一实例类中的另一个方法时,实例解析是否仍然发生?

关于一点不确定(这是CLR的纯实现细节),但认为是。


静态方法不能被嘲笑,如果您进行TDD甚至只是单元测试,这会严重损害您的测试。
流浪者

@trampster为什么?这只是一个逻辑。您可以轻松嘲笑您给的东西吗?获得正确的行为。无论如何,许多静态方法将成为函数中的私有逻辑。
M. Mimpen '18 -10-8

@ M.Mimpen,只要您将其保留为小块私人文件,您可以罚款,如果它是公共方法,并且您在其他关闭时使用它,并且需要更改其在测试中的行为,那么您将被卡住,例如文件IO或数据库访问或网络电话等,如果放的静态方法将成为unmockable,除非像你说你注入mockable依赖作为参数传递给静态方法
trampster

-2

静态方法更快,但OOP更少,如果您将使用设计模式,则静态方法可能是错误的代码,可以在没有静态的情况下更好地编写业务逻辑,诸如文件读取,WebRequest等常见功能可以更好地实现为静态...您的问题没有通用性回答


16
您没有对您的要求提供任何论据。
ymajoros 2012年

2
@ fjch1997 2名支持者似乎在考虑其他问题(以其价值而言)。谈到downvotes是stackexchange鼓励的做法:meta.stackexchange.com/questions/135/...
ymajoros
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.