使用静态方法与实例化包含方法的类的性能
Posted
技术标签:
【中文标题】使用静态方法与实例化包含方法的类的性能【英文标题】:Performance of using static methods vs instantiating the class containing the methods 【发布时间】:2010-09-17 04:30:26 【问题描述】:我正在使用 C# 开发一个项目。以前的程序员不了解面向对象编程,所以大部分代码都在巨大的文件中(我们谈论的是大约 4-5000 行),分布在数十个甚至数百个方法中,但只有一个类。重构这样一个项目是一项艰巨的任务,所以我现在已经学会了接受它。
每当在其中一个代码文件中使用方法时,都会实例化该类,然后在对象实例上调用该方法。
我想知道这样做是否有任何明显的性能损失?我是否应该“暂时”将所有方法设为静态,最重要的是,应用程序是否会以任何方式从中受益?
【问题讨论】:
我觉得这个应该迁移到CS.SE 【参考方案1】:在我看来,创建一个对象只是这样你就可以调用一个看似对对象没有副作用的方法(根据你的描述,我假设这一点),这对我来说似乎很愚蠢。在我看来,更好的折衷方案是拥有几个全局对象并使用它们。这样,您可以将通常是全局的变量放入适当的类中,以使它们的范围略小。
从那里你可以慢慢地移动这些对象的范围越来越小,直到你有一个像样的 OOP 设计。
再说一次,我可能会使用不同的方法;)。
就个人而言,我可能会专注于结构和对其进行操作的函数,并尝试将它们一点一点地转换为具有成员的类。
至于问题的性能方面,静态方法应该稍微快一些(但不多),因为它们不涉及构造、传递和解构对象。
【讨论】:
【参考方案2】:从here 开始,每次调用实例方法时,静态调用比构造实例快 4 到 5 倍。但是,我们仍然只讨论每次调用几十纳秒,所以你不太可能注意到任何好处,除非你有数百万次调用方法的非常紧密的循环,并且你可以通过在外部构造单个实例来获得相同的好处该循环并重用它。
由于您必须更改每个调用站点以使用新的静态方法,您最好将时间花在逐步重构上。
【讨论】:
很棒的文章。我刚开始这样做是因为我对更快的东西感兴趣。 “While object.read”或 For-Loop 和 IF。而这篇文章只是我个人研究之后的完美之作。很不错。现在我有一个非常大的对象,可以在很多地方访问,我想知道是否值得将它逐个方法传递到它必须去的地方,或者简单地创建一个全局变量类并在那里访问它。很难测试什么会更快...? :(【参考方案3】:我认为您已经按照您提出的方式部分回答了这个问题:您的代码中是否存在任何明显的性能损失?
如果惩罚不明显,您根本不需要做任何事情。 (尽管毫无疑问,代码库将从逐渐重构为受人尊敬的 OO 模型中受益匪浅)。
我想我的意思是,只有当您发现性能问题是一个问题时,它才是一个问题。
【讨论】:
【参考方案4】:这取决于该对象还包含什么——如果“对象”只是一堆函数,那么它可能不是世界末日。但是如果对象包含一堆其他对象,那么实例化它会调用它们的所有构造函数(和析构函数,当它被删除时),你可能会得到内存碎片等等。
也就是说,听起来性能并不是你现在最大的问题。
【讨论】:
【参考方案5】:我在工作的地方也遇到过类似的问题。我之前的程序员创建了 1 个控制器类,其中所有 BLL 函数都被转储了。
我们现在正在重新设计系统,并根据它们应该控制的内容创建了许多控制器类,例如
用户控制器、地理控制器、购物控制器...
在每个控制器类中,它们都有静态方法,这些方法使用单例模式调用缓存或 DAL。
这为我们带来了 2 个主要优势。它稍微快一点(大约快 2-3 倍,但这里说的是纳秒;P)。另一个是代码更干净
即
ShoppingController.ListPaymentMethods()
而不是
new ShoppingController().ListPaymentMethods()
如果类不维护任何状态,我认为使用静态方法或类是有意义的。
【讨论】:
【参考方案6】:您必须确定重写的目标。如果您想拥有良好的可测试、可扩展和可维护的 OO 代码,那么您可以尝试使用对象及其实例方法。毕竟这是我们在这里讨论的面向对象编程,而不是面向类编程。
当您定义实现接口的类并执行实例方法时,伪造和/或模拟对象非常简单。这使得彻底的单元测试快速而有效。
此外,如果您要遵循良好的 OO 原则(请参阅http://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29 上的 SOLID)和/或使用设计模式,您肯定会进行大量基于实例、基于接口的开发,而不是使用许多静态方法。
至于这个建议:
对我来说创建一个对象只是为了调用一个方法似乎很愚蠢 似乎对对象没有副作用(根据您的描述,我假设这一点)。
我在 dot net 商店中经常看到这种情况,对我来说这违反了封装,这是一个关键的 OO 概念。我应该无法通过该方法是否为静态来判断该方法是否具有副作用。除了打破封装之外,这意味着如果/当您修改它们以产生副作用时,您将需要将方法从静态更改为实例。我建议您阅读关于这一原则的开放/封闭原则,看看上面引用的建议方法是如何考虑到这一点的。
记住那句老话,“过早的优化是万恶之源”。我认为在这种情况下,这意味着不要使用不适当的技术(即面向类的编程)跳过箍,直到你知道你有性能问题。即使然后调试问题并寻找最合适的。
【讨论】:
【参考方案7】:静态方法要快得多并且使用的内存要少得多。有一种误解,认为它只是快一点。只要你不把它放在循环上,它就会快一点。顺便说一句,有些循环看起来很小,但实际上并不是因为包含循环的方法调用也是另一个循环。您可以区分执行渲染功能的代码。不幸的是,在许多情况下,内存要少得多。实例允许与姐妹方法轻松共享信息。静态方法会在需要时询问信息。
但就像驾驶汽车一样,速度带来责任。静态方法通常比它们的实例对应物具有更多的参数。因为实例会负责缓存共享变量,所以您的实例方法看起来会更漂亮。
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(实例)与 File(静态)。
静态方法是可测试的。事实上,甚至比实例一次更可测试。方法 GetSum(x,y) 不仅可用于单元测试,还可用于负载测试、集成测试和使用测试。实例方法对单元测试很好,但对所有其他测试(这比单元测试更重要)来说都是可怕的,这就是为什么我们这些天会遇到这么多错误的原因。使所有方法无法测试的是没有意义的参数,例如 (Sender s, EventArgs e) 或像 DateTime.Now 这样的全局状态。事实上,静态方法在可测试性方面非常出色,以至于你在新 Linux 发行版的 C 代码中看到的 bug 比普通的 OO 程序员要少(我知道他满嘴狗屁)。
【讨论】:
【参考方案8】:在 php 中无效, 对象方法更快:http://www.vanylla.it/tests/static-method-vs-object.php
【讨论】:
以上是关于使用静态方法与实例化包含方法的类的性能的主要内容,如果未能解决你的问题,请参考以下文章