使用静态方法与实例化包含方法的类的性能比较

32

我正在做一个C#项目。之前的程序员不懂面向对象编程,所以大多数代码都在一个类中,分散在几十个甚至上百个方法中(总代码行数约4-5000行)。对这样的项目进行重构是一项巨大的任务,因此现在我已经半适应了现状。

每当一个代码文件中使用到一个方法时,都会实例化该类,然后在该对象实例上调用该方法。

我想知道这样做是否会有明显的性能损失?我是否应该暂时将所有方法设置为静态方法,并且最重要的是,这样做是否会为应用程序带来好处?


我认为这应该迁移到CS.SE。 - Ky -
8个回答

30

这里得知,静态调用方法比每次调用实例方法都构造一个实例快4到5倍。然而,在每次调用中只涉及数十纳秒时间,因此除非您有真正紧密的循环调用方法数百万次,否则您不太可能注意到任何好处,并且通过在循环之外构造单个实例并重复使用它,您可以获得相同的好处。

由于您必须更改每个调用站点以使用新的静态方法,因此您最好花时间逐步进行重构。


很棒的文章。我开始做这个因为我对什么更快感兴趣。“While object.read”还是For-Loop和IF。而这篇文章正好是在我个人研究之后最完美的东西。非常好。现在我有一个非常大的对象,在许多地方都被访问,我想知道是否值得逐个方法传递到它应该去的位置,还是简单地创建一个全局变量类并在那里访问它。很难测试哪个会更快...?:( - Matthis Kohli

12

我曾经遇到过类似的问题,我的前任程序员创建了一个控制器类,将所有的BLL函数都倒入其中。

我们现在正在重新设计系统,并根据它们应该控制的内容创建了许多控制器类,例如UserController、GeographyController、ShoppingController等。

在每个控制器类内部,它们都有使用单例模式调用缓存或DAL的静态方法。

这给我们带来了两个主要优点。它稍微快一些(大约快2-3倍,但这只是纳秒级别);另一个是代码更加简洁。

即:

ShoppingController.ListPaymentMethods()

而不是

new ShoppingController().ListPaymentMethods()

我认为如果类没有维护任何状态,使用静态方法或类是有意义的。


7

这取决于该对象包含的其他内容--如果"对象"只是一堆函数,那么这可能不是世界末日。但如果对象包含许多其他对象,则实例化它将调用所有它们的构造函数(以及在删除时调用它们的析构函数),你可能会遇到内存碎片等问题。

话虽如此,现在看来性能并不是你最大的问题。


6
你需要确定重写的目标。如果你想要漂亮、可测试、可扩展和易于维护的面向对象编程代码,那么你可以尝试使用对象及其实例方法。毕竟我们在谈论面向对象编程,而不是基于类的编程。
当你定义实现接口并执行实例方法的类时,伪造和/或模拟对象非常简单。这使得彻底的单元测试快速而有效。
此外,如果你遵循良好的面向对象原则(参见SOLID:http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29)和/或使用设计模式,你肯定会进行大量的基于实例、基于接口的开发,而不是使用许多静态方法。
至于这个建议:
“对我来说,创建一个对象只是为了调用一个看起来没有副作用的方法(从你的描述中我假设是这样),这似乎很愚蠢。”
我在dot net商店经常看到这种情况,对我来说这违反了封装,这是一个关键的面向对象概念。我不应该通过方法是否是静态的来判断它是否具有副作用。除了破坏封装之外,这意味着你需要将方法从静态修改为实例,如果/当你修改它们以具有副作用。我建议你在这个问题上阅读一下开闭原则,并了解上述建议如何考虑这一点。
记住那句老话,“过早优化是万恶之源”。我认为在这种情况下,这意味着不要使用不合适的技术(即基于类的编程),直到你知道你有性能问题。即使出现问题,也要调试问题并寻找最合适的解决方案。

6
静态方法速度更快,占用内存更少。有一个误解,认为它只是稍微快一点。只要不将其放在循环中,它就会稍微快一点。顺便说一句,有些循环看起来很小,但实际上并不小,因为包含循环的方法调用也是另一个循环。你可以通过执行渲染功能的代码来区分差异。在许多情况下,占用内存更少是不幸的事实。实例允许与姐妹方法轻松共享信息。静态方法需要在需要时请求信息。
但就像开车一样,速度带来责任。静态方法通常比它们的实例对应物具有更多参数。因为实例将负责缓存共享变量,所以你的实例方法将看起来更漂亮。
ShapeUtils.DrawCircle(stroke, pen, origin, radius);

ShapeUtils.DrawSquare(stroke, pen, x, y, width, length);

VS

ShapeUtils utils = new ShapeUtils(stroke,pen);

util.DrawCircle(origin,radius);

util.DrawSquare(x,y,width,length);

在这种情况下,如果实例变量大多数时间由所有方法使用,则实例方法非常值得使用。实例不是关于状态,而是关于分享,尽管共同状态是分享的一种自然形式,但它们并不相同。经验法则是这样的:如果方法与其他方法紧密耦合 --- 它们彼此喜欢,并且当一个被调用时,另一个需要被调用,它们可能共享同一杯水--- 那么它应该被设置为实例。将静态方法转换为实例方法并不难。你只需要将共享参数作为实例变量放置即可。相反,另一种方法要难得多。
或者你可以制作一个代理类来连接静态方法。虽然在理论上可能看起来更低效,但实践证明了不同的故事。这是因为每当您需要调用DrawSquare一次(或循环中),您会直接进入静态方法。但是每当您将其与DrawCircle一起反复使用时,您将使用实例代理。一个例子是System.IO类FileInfo(实例)vs File(静态)。
静态方法是可测试的。实际上,甚至比实例方法更可测试。方法GetSum(x,y)非常适合进行单元测试,负载测试,集成测试和使用测试。实例方法对于单元测试很好,但对于其他所有测试(这比单元测试更重要)都很糟糕,这就是为什么我们现在会有那么多错误。使所有方法都不可测试的是没有意义的参数,例如(发送者s,事件args)或全局状态如DateTime.Now。实际上,静态方法在可测试性方面非常出色,以至于您在新Linux发行版的C代码中看到的错误比您平均的OO程序员要少(我知道他充满了废话)。

3

我认为你在提问时已经部分回答了这个问题:你的代码中是否有任何显著的性能惩罚。

如果惩罚并不明显,你不一定需要采取任何行动。(尽管毫无疑问,逐步重构成体面的面向对象模型将会极大地提升代码库的效益)。

我的意思是,只有当你注意到性能问题时,它才是一个问题。


2
我认为仅仅为了调用一个看似没有对对象产生任何影响的方法(我假设你是这样描述的),创建一个对象是很愚蠢的。更好的折中方案是使用几个全局对象,只需使用它们即可。这样,您可以将通常为全局的变量放入适当的类中,使其范围稍微缩小一些。
从那里开始,您可以逐渐将这些对象的范围缩小到更小,直到拥有合理的面向对象设计。
然而,我可能会采用不同的方法 ;)
就我个人而言,我可能会专注于结构和操作它们的函数,并尝试逐渐将其转换为具有成员的类。
至于性能方面的问题,静态方法应该会稍微快一些(但不会太多),因为它们不涉及构造、传递和解构对象。

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接