静态方法与实例方法的性能
Posted
技术标签:
【中文标题】静态方法与实例方法的性能【英文标题】:Performance of static methods vs instance methods 【发布时间】:2012-08-30 01:19:02 【问题描述】:我的问题与特征及其可扩展性有关。对于这种情况,假设所有类定义都在一个程序集中,并且需要多个离散指针类型。
考虑:
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相反。
我的问题是:
当保持状态不是问题(不需要字段或属性)时,使用静态类总是更好吗?
如果有大量这些静态类定义(例如 100 个,每个都有多个静态方法),与相同数量的实例类定义相比,这会对执行性能或内存消耗产生负面影响?
当调用同一个实例类中的另一个方法时,实例解析是否仍然发生?例如,在同一实例的DoOperation1
中使用this.DoOperation2("abc")
之类的[this] 关键字。
【问题讨论】:
Performance of using static methods vs instantiating the class containing the methods的可能重复 “实例解析”是什么意思?在 IL 级别上,“this”指针与任何其他局部变量一样可用。事实上,在一些旧的 CLR/JIT 版本上,您可以在 NULL 上调用实例方法,前提是它没有触及“this” - 代码只是飞过并崩溃了。现在 CLR/JIT 包含显式的 null-检查每个成员调用.. > vijaymukhi.com/documents/books/ilbook/chap8.htm 和“调用实例”而不是“调用”。前者需要“this”参数,而后者不需要。 @Quetzalcoatl 很抱歉造成混淆,问题是从同一个实例中获取更多方法,以及是否需要将实例解析为自身。 @quetzalcoatl 我以为他的意思是,“当类调用自身的实例方法时,编译器是否会检查this
是否指向某些东西?”
【参考方案1】:
当保持状态不是问题时(没有字段或属性 必需),使用静态类总是更好吗?
我会说,是的。在声明 static
时,您声明了一个 intent 无状态执行(这不是强制性的,而是人们期望的某种意图)
这些静态类有相当多的地方(比如 100 例如,每个都有许多静态方法)这会影响 相比而言,执行性能或内存消耗为负 具有相同数量的实例类?
不要这么认为,除非您确定静态类真的无限制,否则很容易搞乱内存分配并导致内存泄漏。
当 [this] 关键字用于调用同一个内的另一个方法时 实例类,实例解析还会发生吗?
不确定,关于这个点(这纯粹是CLR的实现细节),但认为是的。
【讨论】:
不能模拟静态方法,如果你做 TDD 甚至只是单元测试,这会对你的测试造成很大的伤害。 @trampster 为什么?这只是一个逻辑。你可以轻松地模拟你给它的东西吗?获得正确的行为。无论如何,许多静态方法将是函数中的私有逻辑。 @M.Mimpen 只要你把它留给小的私人部分就可以了,文件 IO 或数据库访问或网络调用等,如果放在静态方法中将变得不可模拟,除非像你说你将可模拟的依赖项作为参数注入静态方法【参考方案2】:静态方法更快,但 OOP 更少。如果您将使用设计模式,则静态方法可能是糟糕的代码。业务逻辑最好写成非静态的。文件读取、WebRequest 等常用函数作为静态函数更好。您的问题没有统一的答案。
【讨论】:
你没有为你的主张提供任何论据。 这里有论点 1. instance 可能更适合设计模式和业务逻辑,2. static 可能更适合文件读取、Web 请求【参考方案3】:理论上,在所有其他条件相同的情况下,静态方法的性能应该略好于实例方法,因为额外隐藏了this
参数。
在实践中,这几乎没有什么区别,以至于它会隐藏在各种编译器决策的噪音中。 (因此,两个人可以“证明”一个人比另一个人更好,但结果不一致)。尤其是因为this
通常是在一个寄存器中传递的,并且通常在该寄存器中开始。
最后一点意味着,理论上,我们应该期望将对象作为参数并对其进行某些操作的静态方法比作为同一对象上的实例的等效方法稍差。尽管如此,差异是如此之小,以至于如果您尝试测量它,您可能最终会测量其他一些编译器决定。 (特别是因为如果该引用一直在寄存器中的可能性也很高)。
真正的性能差异将归结为您是否人为地让内存中的对象执行自然应该是静态的事情,或者您是否正在以复杂的方式将对象传递链纠缠起来以执行自然应该是实例的事情.
因此对于数字 1。当保持状态不是问题时,最好是静态的,因为这就是静态的用途。这不是性能问题,尽管有一个总体规则可以很好地使用编译器优化 - 更有可能有人努力优化正常使用的案例而不是奇怪使用的案例。
数字 2。没有区别。每个成员都有一定的每类成本,包括有多少元数据,实际 DLL 或 EXE 文件中有多少代码,以及会有多少 jitted 代码。无论是实例还是静态,都是一样的。
对于第 3 项,this
与 this
一样。但请注意:
this
参数在特定寄存器中传递。在同一个类中调用实例方法时,它可能已经在该寄存器中(除非它被隐藏并且由于某种原因使用了该寄存器),因此不需要任何操作来将 this
设置为它需要的值设置为。这在一定程度上适用于例如方法的前两个参数是它调用的前两个参数。
因为很明显this
不为空,所以在某些情况下可以使用它来优化调用。
由于很明显this
不为空,这可能会再次提高内联方法调用的效率,因为为伪造方法调用而生成的代码可以省略一些它可能需要的空值检查.
也就是说,空检查很便宜!
值得注意的是,作用于对象的通用静态方法,而不是实例方法,可以减少http://joeduffyblog.com/2011/10/23/on-generics-and-some-of-the-associated-overheads/ 中讨论的一些成本,在给定类型不调用给定静态的情况下。正如他所说,“顺便说一句,扩展方法是让泛型抽象更加付费的好方法。”
但是,请注意,这仅与方法使用的其他类型的实例化有关,否则不存在。因此,它确实不适用于很多情况(其他一些实例方法使用该类型,其他一些其他代码使用该类型)。
总结:
-
大多数情况下,实例与静态的性能成本都可以忽略不计。
通常会在您滥用静态的地方产生成本,反之亦然。如果您不在静态和实例之间做出决定,则更有可能获得正确的结果。
在极少数情况下,另一种类型的静态泛型方法比实例泛型方法创建的类型更少,这可能使其有时变得很少使用(并且“很少使用" 指的是它在应用程序的生命周期中使用的类型,而不是它被调用的频率)。一旦你了解了他在那篇文章中所说的内容,你就会发现它与大多数静态与实例决策 100% 无关。编辑:而且它主要只有 ngen 的成本,而不是 jitted 代码。
编辑:关于空检查有多便宜的注释(我在上面声称)。 .NET 中的大多数空值检查根本不检查空值,而是继续他们将要做的事情,假设它会起作用,如果发生访问异常,它会变成NullReferenceException
。因此,大多数情况下,当 C# 代码在概念上涉及空检查时,因为它正在访问实例成员,如果成功,成本实际上为零。一个例外是一些内联调用,(因为他们想要表现得好像他们调用了一个实例成员)并且他们只是点击一个字段来触发相同的行为,所以它们也非常便宜,而且它们仍然经常被排除在外(例如,如果该方法的第一步涉及按原样访问字段)。
【讨论】:
您能否评论一下静态与实例问题是否对缓存一致性有任何影响?依赖一个或另一个更有可能导致缓存未命中?是否有一个很好的大纲来解释原因? @scriptocalypse 不是真的。指令缓存不会有任何区别,在那个级别上,通过this
或通过显式参数访问数据并没有太大区别。这里更大的影响是数据与相关数据的接近程度(值类型字段或数组值比引用类型字段中的数据更接近)和访问模式。
“理论上,我们应该期望一个将对象作为参数并对其执行某些操作的静态方法比作为同一对象上的实例的等效方法稍差。”--你的意思是如果上面的示例方法将参数作为对象而不是字符串,非静态更好?例如:我的静态方法将对象作为参数并将其序列化为字符串并返回字符串。您是否建议在这种情况下使用非静态?
@batmaci 我的意思是很有可能obj.DoSomehting(2)
会比DoSomething(obj, 2)
稍微便宜一点,但正如我也说过的那样,差异是如此之小,因此取决于最终可能会有所不同的微小事物编译它根本不值得担心。如果您正在做一些昂贵的事情(相对于此处播放的那种差异)将某些内容序列化为字符串,那么它尤其没有意义。
在这个本来很好的答案中缺少一个可能很明显但很重要的东西:实例方法需要一个实例,而创建一个实例并不便宜。即使是默认的ctor
仍然需要初始化所有字段。一旦你已经有了一个实例,这个答案就适用(“所有其他事情都是平等的”)。当然,昂贵的cctor
也会使静态方法变慢,但这仅在第一次调用时同样适用于实例方法。另见docs.microsoft.com/en-us/previous-versions/dotnet/articles/…以上是关于静态方法与实例方法的性能的主要内容,如果未能解决你的问题,请参考以下文章
GroovyGroovy 扩展方法 ( 扩展静态方法示例 | 扩展实例方法示例 | 扩展实例方法与扩展静态方法代码相同 )