我是否正确理解了过早优化?

5
我一直在苦恼于我正在编写的应用程序,我认为我的问题是过早优化。完美主义者的一面想要第一次就使所有东西都达到最佳和完美状态,但我发现这让设计变得更加复杂。我倾向于将尽可能多的功能塞入一个函数中,而不是编写小型、可测试的函数,这些函数能够很好地完成简单的任务。
例如,我避免了多次从数据库获取相同信息的操作,但代码变得更加复杂。我有一部分想法是不必担心冗余的数据库调用。这样做会使编写正确代码变得更容易,而且获取的数据量也很小。但另一部分则感觉非常肮脏和不干净。 :-)
我倾向于多次访问数据库,我认为这是正确的做法。重要的是我完成项目,我感觉因为这类优化问题而陷入困境。我的问题是:在避免过早优化时,这是否是正确的策略?

之前已经有很多关于此话题的讨论了:http://stackoverflow.com/search?q=premature+optimization - Alex Korban
如果您告诉我们您使用的编程语言和数据库,人们可能能够给出说明性示例。我知道您的问题很普遍,但是示例通常有助于解决问题。 - detly
@detly:这是一个由 PHP/MySQL 支持的 Web 应用程序。 - Ed Mazur
3个回答

20

总的来说,这是一种正确的策略。让代码运行起来,并彻底覆盖自动化测试。

然后,您可以在程序由性能分析器控制时运行自动化测试,以找出程序花费时间和/或内存的位置。这将向您展示需要进行优化的位置。

它将展示给您如何优化可工作的代码,而不是当所有代码组合在一起时可能无法正常工作的代码。

您不希望出现优化失效的代码。


我一直没有记住的引用来自Mich Ravera:

如果它不起作用,那么它有多快也无所谓。


12
“你不希望的是失败得最优。” - 这是一个很好的表述! - Chad Levy
@Paperjam:有人说过类似的话,只是我不太记得具体是什么了。 - John Saunders

5
我们应该忘记小的效率问题,大约97%的时间都是这样:过早优化是万恶之源。-- 霍尔
尽管@John Saunders解释得很好,但仅应用TDD可能无法完全解决您的问题。我坚持TDD,当您正确地执行TDD并且能够有效地应用重构时,通常会得到更简洁的代码,并且您知道它是有效的。毫无争议。
然而,我看到太多开发人员编写性能忽略的代码 - 避免过早优化不是编写松散/懒惰/幼稚代码的借口。编写单元测试并不能防止这种情况发生。虽然编写单元测试的人可能是更好的编码人员,而更好的编码人员更少地编写糟糕的代码。
确保编写测试,并将性能测试包括在您的测试套件中以满足您的利益相关者所识别的场景。例如“在3秒内以Xml格式检索特定供应商的100个打折产品,并包括库存水平”。
“过早优化”的谬论不应指导软件开发。-- Randall Hyde 如果你将性能问题拖延到太晚,你可能会发现更改变得太难或成本过高。
一些文章
- 过早优化的谬论 - 过早优化

我恐怕必须在某种程度上不同意。写“可能能够正常工作的最简单的东西”很可能会导致编写天真的代码。但通过使用TDD,您将获得出色的代码覆盖率 - 足以使您能够使用这些测试来驱动性能调查和修正过程。 - John Saunders
@John Saunders 我不知道你反对什么?我的答案试图鼓励原帖作者根据利益相关者的绩效期望编写测试。我已经重新措辞以便更清晰明了。 - Robert Paulson
我可以澄清一下,你的代码必须通过所有测试,包括性能测试。然而,必须在让代码正常工作和让代码良好工作之间取得合理的平衡。表现不佳的代码可能需要重新设计以达到性能目标。但是,如果它通过了所有自动化测试,那么重设计可以作为重构进行,更有信心地确保代码仍然有效。 - John Saunders
@John Saunders,我仍然同意你的观点,我相信我所说的任何话都没有与之相矛盾。 - Robert Paulson
这可能只是强调的问题。在代码能够正常工作之前,我会推迟性能测试。当然,我会从经验中学习:也许已经了解到某些设计或某些代码模式很昂贵,那么就不要使用它们。但除此之外,我想确保我正在优化可行的代码。 - John Saunders
我本来会因为你引用Tony Hoare的话不完整而给你一个-1的评分。但是你提到了Ubiquity的优秀文章《过早优化的谬论》,这已经弥补了不足。在软件开发中,有许多常常相互冲突的要求,这篇文章讨论了如何从SW项目的一开始就对性能要求进行口头上的支持,以及后来如何几乎不可能进行改进。这是一个没有绝对真理的职业,然而性能要求通常被视为绝对的非真实性……+1 - Olof Forshell

1
Knuth 引用中最关键的一点是“小家子气,大失误”。这就是他最终描述的过早进行优化的人——当有很多钱可节省时,却只为省下几分钱而争论不休,并且努力维护他们的“优化”(请注意他在此处使用引号的方式)软件。我发现很多人经常只引用 Knuth 论文的一小部分。值得注意的是,他的论文主张使用 goto 来加速软件中的关键执行路径。更完整的引用如下:
[...] 如果n的平均值约为20,并且在程序中执行搜索例程大约一百万次左右,那么这将明显节省总运行速度。使用goto进行此类循环优化并不难学习,正如我所说,它们只适用于程序的一小部分,但它们通常会产生可观的节省。 [...] 许多现今软件工程师共享的传统智慧是忽略小规模效率问题;但我认为这只是对那些吝啬而愚蠢的程序员滥用优化的过度反应,他们无法调试或维护他们的“优化”程序。在成熟的工程学科中,轻松获得12%的改善从来不被视为边缘;我认为在软件工程中应该有同样的观点。当然,如果只需要完成一项任务,我不会费心进行这样的优化,但是当涉及到准备高质量的程序时,我不想限制自己只使用拒绝这种效率的工具。毫无疑问,追求效率的目标会导致滥用。程序员浪费了大量时间思考或担心其程序的非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时具有强烈的负面影响。我们应该忘记小效率,例如97%的时间;过早地优化是万恶之源。通常对程序的哪些部分真正关键做出先验判断是错误的,因为使用测量工具的程序员的普遍经验是他们的直觉猜测失败了。在使用这些工具七年后,我已经确信,所有从现在开始编写的编译器都应该被设计为向所有程序员提供反馈,指示他们程序的哪些部分成本最高;除非明确关闭,否则应自动提供此反馈。程序员知道他的例程的哪些部分真正重要之后,像双倍循环这样的转换将是值得的。请注意,此转换引入了go to语句--其他几个循环优化也是如此。
这段话来自一个曾经非常关注微观层面性能的人,当时(优化器现在已经进步了很多),他利用了goto来提高速度。

而 Knuth 所提出的“过早优化”的核心是:

  1. 基于直觉/迷信/人类直觉进行优化,没有过去的经验或测量数据(盲目优化而不知道自己在做什么)。
  2. 以省几分钱为代价的优化(无效的优化)。
  3. 寻求所有东西的绝对最高效率。
  4. 非关键路径中寻求效率。
  5. 在你几乎无法维护/调试代码时尝试优化。

这些都与您优化的时间无关,而与经验和理解有关--从理解关键路径到理解实际提供性能的内容。

像测试驱动开发和主要关注接口设计等事项并没有在 Knuth 的论文中涵盖。这些是更现代的概念和想法。他主要关注实现。

尽管如此,这是对Knuth建议的一个很好的更新——通过测试和接口设计首先确立正确性,并留下优化空间而不破坏一切。
如果我们试图应用现代解释,我会在其中加入“交付”。即使您正在通过测量收益来优化软件的真正关键路径,如果它从未交付,那么世界上最快的软件也毫无价值。记住这一点应该有助于您做出更明智的妥协。
“我倾向于只是多次访问数据库,我认为这是正确的做法。完成项目更重要,我觉得我因为这样的优化而陷入困境。我的问题是:在避免过早优化时,这是正确的策略吗?”
根据上述一些要点,你需要自己发展最佳判断力,因为你最了解自己的需求。
我建议一个至关重要的因素是,如果这是处理重负载的性能关键路径,请以一种方式设计公共接口,以便留出足够的优化空间。
例如,不要为具有客户端依赖性的粒子系统设计 Particle 接口。这样做留下了优化的空间很小,因为你只能使用封装状态和单个粒子的实现。在这种情况下,您可能需要对代码库进行级联更改以进行优化。如果赛车道只有10米长,那么赛车无法利用其速度。相反,应该设计面向 ParticleSystem 接口,该接口聚合了一百万个粒子,例如通过高级操作尽可能批量处理粒子。这样做可以为您留下足够的空间进行优化,而不会破坏设计,如果您发现需要优化,可以随时进行优化。

我完美主义者希望第一次通过就使所有东西都达到最佳状态和完美状态,但我发现这会使设计变得非常复杂。

现在这部分听起来有点过早。通常,您的第一次尝试应该是简单的。即使您正在执行一些冗余工作,简单性通常与相当快的速度相较手牵手。
无论如何,我希望这些观点能够帮助您考虑更多事情。

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