什么时候优化过早?

时间:2008-12-22 03:50:11

标签: optimization premature-optimization

正如Knuth所说,

  

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。

这是Stack Overflow中经常出现的问题,例如“哪个是最有效的循环机制”,“SQL优化技术?”等问题。 (and so on)。这些优化提示问题的标准答案是分析您的代码并查看它是否首先出现问题,如果不是,则因此不需要您的新技术。

我的问题是,如果某种特定技术不同但不是特别模糊或混淆,那真的可以被视为过早优化吗?

以下是Randall Hyde撰写的一篇名为 The Fallacy of Premature Optimization 的相关文章。

20 个答案:

答案 0 :(得分:100)

Don Knuth开始了 literate programming 运动,因为他认为计算机代码最重要的功能是将程序员的意图传达给人类读者。任何使代码难以理解的编码实践都是过早的优化。

在优化名称中引入的某些习语已变得如此受欢迎,以至于每个人都理解并且它们已成为预期,而不是为时过早。例子包括

  • 在C中使用指针算法代替数组表示法,包括使用

    这样的习语
    for (p = q; p < lim; p++)
    
  • 在Lua中将全局变量重新绑定到局部变量,如

    local table, io, string, math
        = table, io, string, math
    

除了这些习语之外,在你的危险中走捷径

除非

,否则所有优化都为时过早
  • 程序太慢(很多人忘了这一部分)。

  • 您有衡量(个人资料或类似情况),表明优化可以改善

(也可以优化内存。)

直接回答问题:

  • 如果您的“不同”技术使程序难以理解,那么这是一个不成熟的优化

编辑:在回复评论时,使用快速排序而不是更简单的算法(如插入排序)是另一个每个人都理解并期望的习惯用法的例子。 (虽然如果你编写自己的排序例程而不是使用库排序例程,那么希望你有一个非常的理由。)

答案 1 :(得分:39)

恕我直言,90%的优化应该在设计阶段进行,基于所需的电流,更重要的是,未来的要求。如果你不得不拿出一个分析器,因为你的应用程序没有扩展到所需的负载你就太迟了,IMO将浪费大量的时间和精力而无法解决问题。

通常,唯一值得进行的优化是那些在速度或存储或带宽方面的乘数方面可以获得数量级性能提升的优化。这些类型的优化通常与算法选择和存储策略有关,并且极难逆转到现有代码中。它们可能会影响您对实施系统的语言的决定。

所以我的建议是,根据您的要求提前进行优化,而不是根据您的代码进行优化,并查看应用可能的延长使用寿命。

答案 2 :(得分:29)

如果你还没有分析过,那就太早了。

答案 3 :(得分:27)

  

我的问题是,如果特别的话   技术不同但不是   特别模糊或混淆,   真的可以被认为是一个   过早优化?

嗯......所以你手边有两种技术,成本相同(使用,阅读,修改相同),一种效率更高。不,在这种情况下,使用效率更高的人不会为时过早。

中断你的代码编写,以寻找常见的编程结构/库例程的替代方案,即使你知道你所写的相对速度永远不会在某个地方出现更高效的版本。实际上很重要...... 那是过早的。

答案 4 :(得分:9)

这是我在避免过早优化的整个概念中看到的问题。

说出来与做之间存在脱节。

我已经做了很多性能调整,从其他设计良好的代码中挤出了大量因素,似乎没有过早优化。 Here's an example.

几乎在所有情况下,性能欠佳的原因就是我所说的疾驰的一般性,这是使用抽象的多层类和彻底的面向对象设计,其中简单的概念将是少优雅但完全足够。

在教授这些抽象设计概念的教材中,例如通知驱动架构,以及信息隐藏,只需设置对象的布尔属性就可以产生无限的连锁反应效果,给出的理由是什么? ?的 效率

那么,是不是过早优化?

答案 5 :(得分:8)

首先,让代码正常工作。其次,验证代码是否正确。第三,快点。

在第3阶段之前完成的任何代码更改绝对是不成熟的。我不完全确定如何对之前做出的设计选择进行分类(比如使用非常适合的数据结构),但我更喜欢使用抽象方法,而不是那些表现良好的人,直到我在我可以开始使用分析并使用正确(但通常很慢)的参考实现来比较结果的阶段。

答案 6 :(得分:7)

你似乎在谈论的是优化,比如使用基于散列的查找容器与像数组一样的索引容器,当进行大量的键查找时。这是过早优化,但您应该在设计阶段决定。

Knuth规则的优化类型是最小化最常见代码路径的长度,优化最常运行的代码,例如在汇编中重写或简化代码,使其不那么通用。但是,在确定代码的哪些部分需要这种优化并且优化意愿(可能?)使代码更难理解或维护之前,这样做是没有用的,因此“过早优化是所有邪恶的根源”。

Knuth还说,改变程序使用的算法,而不是优化,总是更好,这是解决问题的方法。例如,稍微调整可能会使优化速度提高10%,从根本上改变程序的工作方式可能会使速度提高10倍。

对此问题上发表的很多其他评论作出反应:算法选择!=优化

答案 7 :(得分:6)

从数据库的角度来看,在设计阶段不考虑优化设计充其量是蛮干的。数据库不容易重构。一旦它们的设计很糟糕(这就是一个不考虑优化的设计,无论你如何试图隐藏在过早优化的废话之后),几乎永远无法从那恢复,因为数据库太基础了整个系统的运作。考虑到您期望的情况的最佳代码,而不是等到有一百万用户并且人们尖叫,因为您在整个应用程序中使用游标,正确设计成本要低得多。其他优化,例如使用可搜索代码,选择看起来最好的索引等等,只有在设计时才有意义。有一个原因可以称之为快速和肮脏。因为它不能很好地工作,所以不要使用快速作为良好代码的替代品。坦率地说,当您了解数据库中的性能调优时,您可以编写更有可能在同一时间内执行良好的代码,或者编写代码执行效果不佳的代码。没有花时间去学习什么是性能良好的数据库设计是开发人员的懒惰,而不是最佳实践。

答案 8 :(得分:5)

格言的要点是,通常,优化是复杂的和复杂的。 通常是,您的架构师/设计师/程序员/维护者需要清晰简洁的代码才能理解正在发生的事情。

如果特定的优化清晰简洁,请随意尝试(但请返回并检查优化是否有效)。重点是在整个开发过程中保持代码清晰简洁,直到性能的好处超过编写和维护优化的诱导成本。

答案 9 :(得分:4)

编程时,许多参数至关重要。其中包括:

  • 可读性
  • 可维护性
  • 复杂性
  • 鲁棒性
  • 正确性
  • 性能
  • 开发时间

优化(追求性能)往往以牺牲其他参数为代价,并且必须与这些领域的“损失”相平衡。

当您可以选择性能良好的众所周知的算法时,预先“优化”前期的成本通常是可以接受的。

答案 10 :(得分:4)

我尝试仅在确认性能问题时进行优化。

我对过早优化的定义是'在未知是性能问题的代码上浪费了。'绝对是优化的时间和地点。但是,诀窍是只花费应用程序性能和额外成本超过性能损失的额外成本。

在编写代码(或数据库查询)时,我努力编写“高效”代码(即执行其预期功能的代码,快速,完整,最简单的逻辑合理。)请注意,“高效”代码不一定与'优化'代码。优化通常会在代码中引入额外的复杂性,从而增加代码的开发和维护成本。

我的建议:当您可以量化效益时,尽量只支付优化成本。

答案 11 :(得分:4)

优化可以在不同的粒度级别进行,从非常高级别到非常低级别:

  1. 从良好的架构,松耦合,模块化等开始。

  2. 为问题选择正确的数据结构和算法。

  3. 优化内存,尝试在缓存中容纳更多代码/数据。内存子系统比CPU慢10到100倍,如果你的数据被分页到磁盘,它的速度要慢1000到10,000倍。对内存消耗持谨慎态度比优化个别指令更有可能带来重大收益。

  4. 在每个函数中,适当使用流控制语句。 (将不可变表达式移到循环体外部。将最常见的值放在开关/案例中等等。)

  5. 在每个语句中,使用最有效的表达式,产生正确的结果。 (乘以转移等)

  6. 关于是否使用除法表达式或移位表达式的选择不是必然过早优化。如果不首先优化架构,数据结构,算法,内存占用和流量控制,那么现在还为时过早。

    当然,如果您没有定义目标绩效阈值,任何优化都为时过早。

    在大多数情况下,要么:

    A)您可以通过执行高级优化来达到目标​​性能阈值,因此不必使用表达式。

    B)即使在执行所有可能的优化之后,您也无法达到目标性能阈值,并且低级优化在性能方面没有足够的差异来证明可读性的损失。

    根据我的经验,大多数优化问题都可以在架构/设计或数据结构/算法级别上解决。通常(但并非总是)要求优化内存占用。但是很少需要优化流量控制和流量控制。表达逻辑。在实际需要的情况下,这很少就足够了。

答案 12 :(得分:2)

诺曼的答案非常好。不知何故,你经常做一些“过早优化”,这实际上是最佳实践,因为已知其他情况完全没有效率。

例如,要添加到Norman的列表中:

  • 在Java(或C#等)中使用StringBuilder连接而不是String + String(在循环中);
  • 避免在C中循环:for (i = 0; i < strlen(str); i++)(因为strlen这里是一个函数调用每次遍历字符串,在每个循环上调用);
  • 在大多数JavaScript实现中,它似乎更快for (i = 0 l = str.length; i < l; i++)并且它仍然可读,所以没问题。

等等。但是,这种微观优化绝不应以代码的可读性为代价。

答案 13 :(得分:2)

对于极端情况,应该留下使用分析器的需要。该项目的工程师应该了解性能瓶颈所在。

我认为“过早优化”是非常主观的。

如果我正在编写一些代码并且知道我应该使用Hashtable,那么我会这样做。我不会以一些有缺陷的方式实现它,然后等待错误报告在一个月或一年之后到达,当有人遇到问题时。

重新设计比从一开始就以明显的方式优化设计成本更高。

显然,第一次会遗漏一些小东西,但这些很少是关键的设计决定。

因此:不优化设计是IMO代码本身的气味。

答案 14 :(得分:1)

除非您发现由于用户或业务需求而需要更多应用程序性能,否则没有理由担心优化。即便如此,在您分析代码之前不要做任何事情。然后攻击占用时间最多的部分。

答案 15 :(得分:1)

我不认为公认的最佳做法是过早的优化。它更多的是根据使用场景,在可能的性能问题上花费时间。一个很好的例子:如果你在证明它是一个瓶颈之前就烧了一周试图优化对象的反射,那么你就会过早地进行优化。

答案 16 :(得分:1)

对我来说过早优化意味着在拥有一个可运行的系统之前,在实际分析它并了解瓶颈之前,尝试提高代码的效率。即便如此,在许多情况下,可读性和可维护性应该优化之前。

答案 17 :(得分:1)

值得注意的是,Knuth的原始引用来自他撰写的一篇论文,其中提到在精心挑选和测量的区域中使用goto作为消除热点的方法。他的引言是一个警告,他补充道,为证明他使用goto来加速这些关键循环的理由是合理的。

  再次,这是一个明显的节省总体运行速度,   如果,比方说,n的平均值约为20,如果是搜索例程   在程序中执行大约一百万次。这样的循环   优化[使用 gotos ]不难学习,就像我一样   他们说,它们只适用于程序的一小部分,但它们   经常产生大量的节省。 [...]

并继续:

  

当今许多软件工程师都持有的传统智慧   要求忽视小的效率;但我相信这是   只是对他们认为正在实践的虐待行为的过度反应   吝啬而愚蠢的程序员,无法调试或维护   他们的优化&#34;程式。在既定的工程学科中   容易获得12%的改善从未被认为是微不足道的;和我   相信同样的观点应该在软件工程中占上风。的   当然,我不打算在单件工作上做出这样的优化,   但是,如果它是准备质量计划的问题,我不想要   限制自己使用那些否认我这种效率的工具[即 goto   本文中的陈述]。

请记住他如何使用&#34;优化&#34;在引号中(软件可能实际上并不高效)。还要注意他是如何批评这些&#34; pennywise-and-pound-愚蠢的&#34;程序员,也是通过建议你做出反应的人应该总是忽略小的低效率。最后,到经常引用的部分:

  

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

...然后更多关于分析工具的重要性:

  

对某个部分进行先验判断往往是错误的   程序是非常关键的,因为它的普遍经验   一直在使用测量工具的程序员就是他们的   直觉猜测失败了。使用这些工具七年后,   我已经确信从现在开始编写的所有编译器都应该是   旨在为所有程序员提供指示内容的反馈   他们的部分计划成本最高;的确,这个反馈   除非是专门的,否则应自动提供   关掉了。

人们在整个地方滥用了他的引用,经常暗示当他的整篇论文提倡微观优化时,微观优化还为时过早!他批评的人群中有一个回应了这个传统智慧&#34;因为他总是忽视小的效率,往往会滥用他的引用,这种引用最初是针对那些阻止所有形式的微观优化的类型。

然而,当有经验的手拿着探查器使用时,它引用了适当应用的微优化。今天类似的等价物可能就像,&#34;人们不应该盲目地尝试优化他们的软件,但是定制内存分配器在应用于关键领域以改善局部性时可以产生巨大的差异参考,&#34; 或,&#34; 使用SoA代表的手写SIMD代码真的很难维护,你不应该在整个地方使用它,但它可以如果由经验丰富且有指导的人员适当地应用,会更快地消耗内存。&#34;

任何时候你都在努力推广上面提到的Knuth推广的精心应用的微优化,最好不要让新手过于兴奋和盲目地采取刺激进行优化,例如重写整个软件以使用goto。这部分是他正在做的事情。他的引言实际上是一个大的免责声明的一部分,就像有人做摩托车跳过火焰火坑可能会增加一个免责声明,业余爱好者不应该在家里尝试这个,同时批评那些没有适当知识和设备的人受伤了。

他认为&#34;过早的优化&#34;那些实际上并不知道他们在做什么的人所采用的优化:不知道是否真的需要优化,没有用适当的工具衡量,也许不了解性质他们的编译器或计算机体系结构,最重要的是&#34; pennywise-and-pound-愚蠢&#34;,这意味着他们忽略了通过尝试捏钱来优化(节省数百万美元)的大机会,以及所有在创建代码时,他们无法再进行有效的调试和维护。

如果你不适合吝啬和愚蠢的&#34;类别,那么你就不会过早地通过Knuth的标准进行优化,即使你正在使用goto来加速一个关键的循环(某些东西不太可能对今天的优化者,但如果确实如此,并且在一个真正关键的领域,那么你就不会过早地优化)。如果你实际上在真正需要的领域应用你所做的任何事情并且真正从中获益,那么你在Knuth眼中做得非常好。

答案 18 :(得分:0)

正如我在类似问题上发布的那样,优化规则是:

1)不要优化

2)(仅限专家)稍后优化

什么时候优化过早?一般

例外可能在您的设计中,或者在大量使用的封装良好的代码中。在过去,我曾经研究过一些时间关键代码(一种RSA实现),其中查看编译器生成的汇编程序并删除内部循环中的单个不必要的指令可以实现30%的加速。但是,使用更复杂算法的速度提高了几个数量级。

在优化时要问自己的另一个问题是“我是否相当于在这里优化300波特调制解调器?”。换句话说,摩尔定律会在不久之后使您的优化无关紧要。只需在问题上投入更多硬件,就可以解决许多缩放问题。

最后但并非最不重要的是,在程序进行得太慢之前进行优化还为时过早。如果你正在谈论它的web应用程序,你可以在负载下运行它以查看瓶颈所在 - 但可能是你将遇到与大多数其他站点相同的扩展问题,并且将应用相同的解决方案。

编辑:顺便提一下,关于链接的文章,我会质疑许多假设。首先,摩尔定律在90年代停止运作并不是真的。其次,用户的时间比程序员的时间更有价值并不明显。大多数用户(至少可以说)不会疯狂地使用可用的每个CPU周期,他们可能正在等待网络做某事。此外,当程序员的时间从实现其他东西转移到用户在手机上时程序所做的事情的削减几毫秒时,存在机会成本。任何比这更长的东西通常不是优化,它是修复bug。

答案 19 :(得分:0)

我看到它的方式是,如果你在不知道在不同情况下可以获得多少性能的情况下进行优化,那么就是过早的优化。代码的目标应该是让人类最容易阅读。