【问题标题】:Am I understanding premature optimization correctly?我是否正确理解过早优化?
【发布时间】:2011-03-01 11:11:22
【问题描述】:

我一直在为我正在编写的应用程序而苦苦挣扎,我想我开始发现我的问题是过早优化。我完美主义的一面希望在第一次通过时就让一切都变得最优和完美,但我发现这让设计变得相当复杂。为了提高效率,我倾向于编写尽可能多的功能,而不是编写可以很好地完成一件简单事情的小型、可测试的函数。

例如,我避免多次访问数据库以获取同一条信息,但代价是我的代码变得更加复杂。我的一部分不想担心冗余的数据库调用。这将使编写正确的代码变得更容易,并且无论如何获取的数据量都很小。这样做时,我的另一部分感觉非常肮脏和不干净。 :-)

我倾向于多次访问数据库,我认为这是正确的做法。更重要的是我完成了这个项目,我觉得我因为这样的优化而被挂断了。我的问题是:这是避免过早优化时使用的正确策略吗?

【问题讨论】:

  • 之前已经有很多讨论了:stackoverflow.com/search?q=premature+optimization
  • 如果您告诉我们您使用的是什么语言和数据库,人们可能会给出说明性示例。我知道您的问题很笼统,但示例通常会有所帮助。
  • @detly:这是一个由 php/mysql 支持的网络应用程序。

标签: premature-optimization


【解决方案1】:

对我来说,Knuth 引用的关键方面是“一分钱一分货”。这就是他最终描述过早的优化器的方式——当有英镑要节省时,有人为了节省便士而讨价还价,并努力维护他们的“优化”(注意他在这里如何使用引号)软件。

我发现很多人经常只引用 Knuth 论文的一小部分。值得注意的是,他的论文主张使用 goto 来加快软件中的关键执行路径

更完整的引用:

[...] 如果 n 的平均值约为 20,并且如果在程序中执行大约一百万次左右的搜索例程,这会显着节省整体运行速度。这种循环优化[使用 gotos] 并不难学习,而且正如我所说,它们只适用于程序的一小部分,但它们通常会产生大量节省。 [...]

当今许多软件工程师所共有的传统观念要求忽略小规模的效率。但我相信 这只是对他们看到的滥用行为的过度反应 由无法调试或 维护他们的“优化”程序。在已建立的工程 纪律 12% 的改进,很容易获得,从不考虑 边缘;我相信同样的观点应该在软件中盛行 工程。当然我不会费心做这样的优化 一个一次性的工作,但是当它是一个准备质量程序的问题时, 我不想将自己限制在拒绝我这样的工具上 效率。

毫无疑问,效率的圣杯会导致滥用。 程序员浪费大量时间思考或担忧 关于,他们程序的非关键部分的速度,以及这些 提高效率的尝试实际上会产生强烈的负面影响 考虑调试和维护。我们应该忘记小 效率,比如说 97% 的时间;过早优化是根本 万恶之源。

先验地判断一个项目的哪些部分通常是错误的 程序真的很关键,因为普遍的经验 一直在使用测量工具的程序员 直觉猜测失败。在使用这些工具七年之后,我确信从现在开始编写的所有编译器都应该设计为向所有程序员提供反馈,表明他们程序的哪些部分成本最高;事实上,这个反馈应该是自动提供的,除非它被特别关闭。

程序员知道他的例程的哪些部分真正重要之后,像加倍循环这样的转换将是值得的。请注意,这种转换引入了go to 语句——其他几个循环优化也是如此。

所以这来自一个实际上非常关注微观层面性能的人,并且当时(优化器现在已经变得更好了),正在利用goto 来提高速度

Knuth 建立“过早优化器”的核心是:

  1. 基于预感/迷信/人类直觉进行优化,没有过去的经验或衡量标准(盲目地优化而不知道自己在做什么)。
  2. 以节省便士超过英镑的方式进行优化(无效的优化)。
  3. 为所有事情寻求绝对的终极效率峰值。
  4. 非关键路径中寻求效率。
  5. 在您几乎无法维护/调试代码时尝试优化。

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

Knuth 的论文没有涉及诸如测试驱动开发和主要关注界面设计之类的内容。这些是更现代的概念和想法。他主要专注于实施。

尽管如此,这是对 Knuth 建议的一个很好的更新——首先通过测试和界面设计来寻求建立正确性,从而为您留出优化空间而不破坏一切。

如果我们尝试应用对 Knuth 的现代解释,我会在其中添加“ship”。即使您正在通过衡量的收益优化软件的真正关键路径,如果世界上最快的软件永远不会发布,它也是毫无价值的。牢记这一点应该有助于您做出更明智的妥协。

我倾向于多次访问数据库,我 认为是正确的举动。更重要的是我完成 项目,我觉得我因为优化而被挂断了 像这样。我的问题是:这是什么时候使用的正确策略 避免过早优化?

当您最了解自己的要求时,考虑到以上几点,您将做出最佳判断。

我建议的一个关键因素是,如果这是一条处理繁重负载的性能关键路径,那么以一种留有足够优化空间的方式设计您的公共接口。

例如,不要设计一个粒子系统,其客户端依赖于Particle 接口。当您只有封装状态和单个粒子的实现可以使用时,这就没有优化的空间了。在这种情况下,您可能必须对代码库进行级联更改才能进行优化。如果道路只有 10 米长,赛车就无法利用它的速度。而是针对聚合一百万个粒子的ParticleSystem 接口进行设计,例如,在可能的情况下使用更高级别的操作来处理散装粒子。如果您发现需要优化,这将为您提供足够的优化空间,而不会破坏您的设计。

我完美主义的一面想让一切都变得最优, 第一次完美,但我发现这很复杂 设计相当多。

现在这部分听起来有点为时过早。一般来说,你的第一遍应该是简单的。简单性通常与相当快的速度并驾齐驱,即使您正在做一些多余的工作,也比您想象的要快。

无论如何,我希望这些要点至少有助于增加一些考虑因素。

【讨论】:

    【解决方案2】:

    总的来说,这是正确的策略。让代码正常工作,并被自动化测试彻底覆盖。

    然后,您可以在程序受分析器控制时运行自动化测试,以找出程序在哪里花费时间和/或内存。这会告诉你在哪里进行优化。

    它将向您展示如何优化工作代码,而不是所有放在一起时可能工作或不工作的代码。

    您不希望代码以最佳方式失败。


    我不记得的那句话来自 Mich Ravera:

    如果它不起作用,它不起作用的速度有多快都没关系。

    【讨论】:

    • "您不希望代码以最佳方式失败。" - 很好的表达方式!
    • @Paperjam:其他人说了类似的话。我只是不记得到底是什么。
    【解决方案3】:

    我们应该忘记小的效率,比如大约 97% 的时间:过早优化是万恶之源。 -- 霍尔

    虽然@John Saunders 指出了这一点,但单独应用 TDD 可能无法完全解决您的问题。我坚持 TDD,当你正确地执行 TDD 时,如果你能有效地应用重构,你通常会得到更精简的代码,并且你知道它可以工作的好处。那里没有争论。

    但是,我看到太多开发人员编写了对性能无知的代码 - 避免过早优化并不是编写草率/懒惰/幼稚代码的借口。编写单元测试并不能阻止这一点。尽管编写单元测试的人可能是更好的编码人员,而更好的编码人员不太容易经常编写糟糕的代码。

    编写测试,并且 将性能测试纳入您的测试套件中,以针对您的利益相关者确定的场景。例如在 3 秒内检索特定供应商的 100 种打折产品,并包括库存水平和 Xml 格式

    “过早优化”与“关注性能”是一回事的谬论不应指导软件开发。 ——兰德尔·海德

    如果您太晚离开性能问题,您可能会发现更改太难或成本太高。

    一些文章

    【讨论】:

    • 恐怕我必须部分不同意。编写“可能工作的最简单的东西”很可能会导致编写幼稚的代码。但是通过使用 TDD,您将获得出色的代码覆盖率 - 足以让您能够使用这些测试来推动性能调查和纠正过程。
    • @John Saunders 我不知道你不同意什么?我的回答试图鼓励 OP 根据利益相关者的绩效期望编写测试。我已经改写了澄清。
    • 我可能会澄清说您的代码必须通过所有测试 - 包括性能测试。但是,必须在使代码正常工作和使代码正常工作之间取得合理的平衡。性能不佳的代码可能需要重新设计以满足性能目标。但如果它通过了所有自动化测试,那么重新设计就可以作为重构进行,对代码仍然有效的信心更大。
    • @John Saunders 我仍然同意你的观点,我不相信我所说的任何与此相矛盾。
    • 也许只是一个强调的问题。我会推迟性能测试,直到代码工作。我当然会从经验中学习:一个人可能已经学会了某些设计或某些代码模式很昂贵,在这种情况下,不要使用它们。但除此之外,我想确保我正在优化工作代码。
    猜你喜欢
    • 2012-01-17
    • 2011-03-27
    • 1970-01-01
    • 2020-04-29
    • 1970-01-01
    • 2011-05-05
    • 2020-11-22
    • 2017-03-03
    • 2011-01-29
    相关资源
    最近更新 更多