关于优化:什么时候优化过早?

When is optimisation premature?

正如Knuth所说,

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

对于"哪种循环机制最有效"、"SQL优化技术"等问题,堆栈溢出通常会出现这种情况。(等等)。这些优化提示问题的标准答案是分析代码并首先查看它是否是一个问题,如果不是,那么您的新技术就不需要了。

我的问题是,如果一个特定的技术是不同的,但不是特别模糊或模糊,那真的会被认为是过早的优化吗?

这是兰德尔海德的一篇相关文章,叫做过早优化的谬误。


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

以优化的名义引入的某些习语已经变得非常流行,以至于每个人都能理解它们,而且它们已经成为人们所期待的,而不是过早的。示例包括

  • 在C语言中使用指针算术而不是数组表示法,包括使用诸如

    1
    for (p = q; p < lim; p++)
  • 将全局变量重新绑定到lua中的局部变量,如

    1
    2
    local table, io, string, math
        = table, io, string, math

除了这些成语,走捷径是危险的。

所有优化都是过早的,除非

  • 程序太慢(很多人忘记了这一部分)。

  • 您有一个度量(配置文件或类似的)显示优化可以改进事情。

(也允许对内存进行优化。)

直接回答问题:

  • 如果您的"不同"技术使程序更难理解,那么这是一个过早的优化。

编辑:作为对注释的响应,使用Quicksort而不是像插入排序这样简单的算法是另一个大家都理解和期待的习惯用法示例。(尽管如果您编写自己的排序例程而不是使用库排序例程,但希望您有一个很好的理由。)


imho,90%的优化应该发生在设计阶段,基于当前的,更重要的是,未来的需求。如果因为应用程序无法扩展到所需的负载而必须取出探查器,那么您就太晚了,IMO将在未能纠正问题的同时浪费大量时间和精力。

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

所以我的建议是,根据你的需求,而不是你的代码,尽早优化,并寻找你的应用程序可能延长的使用寿命。


如果你还没有做过简介,那就太早了。


My question is, if a particular
technique is different but not
particularly obscure or obfuscated,
can that really be considered a
premature optimisation?

嗯。。。所以您手头有两种技术,成本相同(使用、读取、修改的工作量相同),其中一种更有效。不,在这种情况下,使用效率更高的方法不会太早。

中断代码编写以寻找通用编程结构/库例程的替代方案,但很可能会有一个更有效的版本挂在某个地方,即使你知道你所写内容的相对速度实际上并不重要…这还为时过早。


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

说和做之间有一种脱节。

我已经做了很多性能调整,从设计良好的代码中压缩了大量因素,看起来没有过早的优化。这是一个例子。

在几乎每种情况下,性能不理想的原因都是我所说的快速通用性,即使用抽象的多层类和彻底的面向对象设计,在这种情况下,简单的概念将不那么优雅,但完全足够。

在讲授这些抽象设计概念的教材中,如通知驱动的体系结构和信息隐藏,简单地设置一个对象的布尔属性可以对活动产生无限的涟漪效应,给出了什么原因?效率。

那么,这是不是过早的优化?


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

在第3阶段之前进行的任何代码更改都是过早的。我不完全确定如何对之前所做的设计选择进行分类(比如使用非常适合的数据结构),但我更倾向于使用易于编程的抽象,而不是那些性能良好的抽象,直到我处于一个可以开始使用分析并具有正确(尽管通常很慢)引用imp的阶段为止。用于比较结果的元素。


(P)What you seem to be talking about is最优化like a hash-based look up container vs an indexed one like an array when a lot of key look will be done.这不是过早优化,但有些事情你应该在设计阶段决定。(p)(P)The kind of最优化the Knuth rule is about minimuzing the length the most common codecpaths,optiming the code that is run most by example rewriting in Assembly or simplifying the code,making it less general.但是,在这样做的时候,你还没有使用你的某些部分代码需要这一优化和优化的意愿(可以吗?)使守则难以理解或维持,Hence,"过早优化是所有邪恶的核心"。(p)(P)Knuth also say it is always better to,instead of optimizing,change the算法your program used,the approach it takes to a problem.For example when a little twaking might give you a 10%increase of speed with最优化,changing fundamentally the way your program works might make it 10x faster.(p)(P)In reaction to a lot of the other comments posted on this question:Algorithm selection!=优化(p)


从数据库的角度来看,在设计阶段不考虑最优设计充其量是鲁莽的。数据库不容易重构。一旦它们设计得不好(这就是一个不考虑优化的设计,不管你如何试图隐藏过早优化的胡言乱语),几乎永远无法从中恢复,因为数据库对整个系统的操作来说太基础了。考虑到为您期望的情况而正确地设计最佳代码的成本要低得多,而不是等到有一百万用户和人们尖叫时再进行设计,因为您在整个应用程序中都使用了光标。其他优化,如使用可搜索代码、选择看起来是最好的索引等,只在设计时才有意义。有一个原因叫快速和肮脏。因为它永远都不能很好地工作,所以不要用快速代替好的代码。此外,坦率地说,当您了解数据库中的性能调优时,您可以编写在同一时间执行得很好的代码,或者比编写执行得不好的代码所需的时间要短。不花时间了解什么是良好的数据库设计性能是开发人员的懒惰,而不是最佳实践。


最大值的要点是,通常,优化是复杂和复杂的。通常,架构师/设计师/程序员/维护人员需要清晰简洁的代码来理解正在发生的事情。

如果一个特定的优化是清晰和简洁的,可以自由地进行试验(但是一定要回去检查优化是否有效)。重点是在整个开发过程中保持代码的清晰和简洁,直到性能的好处超过编写和维护优化所带来的成本。


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

  • 从良好的体系结构、松耦合、模块化等开始。

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

  • 优化内存,尝试在缓存中容纳更多的代码/数据。内存子系统比CPU慢10到100倍,如果您的数据被分页到磁盘上,它会慢1000到10000倍。对内存消耗保持谨慎比优化单个指令更有可能提供主要收益。

  • 在每个函数中,适当地使用流控制语句。(将不可变表达式移出循环体。将最常用的值放在开关/箱等中。)

  • 在每个语句中,使用产生正确结果的最有效表达式。(乘法与移位等)

  • 选择使用除法表达式还是移位表达式并不一定是过早的优化。如果不首先优化架构、数据结构、算法、内存占用和流控制,那么这样做还为时过早。

    当然,如果没有定义目标性能阈值,任何优化都是过早的。

    在大多数情况下,要么:

    a)您可以通过执行高级优化来达到目标性能阈值,因此无需处理表达式。

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

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


    编程时,一些参数是至关重要的。其中包括:

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

    优化(追求性能)通常以牺牲其他参数为代价,必须与这些领域的"损失"保持平衡。

    当您可以选择性能良好的著名算法时,预先"优化"的成本通常是可以接受的。


    我只在确认性能问题时才尝试优化。

    我对过早优化的定义是"浪费在不知道是性能问题的代码上的精力"。最确定的是,优化是有时间和地点的。然而,诀窍是仅在额外的成本与应用程序的性能有关,并且额外的成本大于性能影响的情况下才花费额外的成本。

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

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


    诺曼的回答很好。不知何故,您通常会做一些"过早的优化",这实际上是最佳实践,因为众所周知,否则做是完全无效的。

    例如,要添加到诺曼列表中:

    • 在String Bug中使用String Bu建器连接(而不是C +等),而不是String + String(在一个循环中);
    • 避免在C类中循环:for (i = 0; i < strlen(str); i++)(因为这里strlen是一个函数调用,每次遍历字符串,在每个循环上调用);
    • 在大多数的JavaScript实现中,执行for (i = 0 l = str.length; i < l; i++)的速度似乎更快,而且它仍然是可读的,所以可以。

    等等。但这种微观优化不应该以代码可读性为代价。


    (P)The need to use a profiler should be left for extreme cases.项目的工程师应当认识到项目的执行情况。(p)(P)我认为"过早的选择"是不可思议的主观。(p)(P)如果我写了一些法典,我知道我应该使用一个Hashtable他们会这样做。I won't implement i t in some flawed way and then wait for the bug report to arrive a month or a year later when some body is having a problem with i t.(p)(P)从《第一阶段裁武条约》可以明显看出,重新设计比选择设计更为昂贵。(p)(P)得到一些小东西会错过第一次在周围,但他们是罕见的关键设计决定。(p)(P)因此,一项设计不是一项可选办法,而是一种标准的守则。(p)


    值得注意的是,Knuth的原话来自他写的一篇文章,该文提倡在精心挑选和测量的区域使用goto,以此来消除热点。他补充说,他的引言是为了证明他使用goto来加速这些关键循环的理由。

    [...] again, this is a noticeable saving in the overall running speed,
    if, say, the average value of n is about 20, and if the search routine
    is performed about a million or so times in the program. Such loop
    optimizations [using gotos] are not difficult to learn and, as I have
    said, they are appropriate in just a small part of a program, yet they
    often yield substantial savings. [...]

    并继续:

    The conventional wisdom shared by many of today's software engineers
    calls for ignoring efficiency in the small; but I believe this is
    simply an overreaction to the abuses they see being practiced by
    pennywise-and-pound-foolish programmers, who can't debug or maintain
    their"optimized" programs. In established engineering disciplines a
    12% improvement, easily obtained, is never considered marginal; and I
    believe the same viewpoint should prevail in software engineering. Of
    course I wouldn't bother making such optimizations on a oneshot job,
    but when it's a question of preparing quality programs, I don't want
    to restrict myself to tools that deny me such efficiencies [i.e., goto
    statements in this context].

    记住他是如何在引号中使用"优化"的(软件可能实际上并不高效)。还要注意,他不仅批评这些"一文不值,一文不值"的程序员,而且还批评那些建议你总是忽略小效率的人。最后,对于经常引用的部分:

    There is no doubt that the grail of efficiency leads to abuse.
    Programmers waste enormous amounts of time thinking about, or worrying
    about, the speed of noncritical parts of their programs, and these
    attempts at efficiency actually have a strong negative impact when
    debugging and maintenance are considered. We should forgot about small
    efficiencies, say 97% of the time; premature optimization is the root
    of all evil.

    …还有一些关于分析工具重要性的更多信息:

    It is often a mistake to make a priori judgments about what parts of a
    program are really critical, since the universal experience of
    programmers who have been using measurement tools has been that their
    intuitive guesses fail. After working with such tools for seven years,
    I've become convinced that all compilers written from now on should be
    designed to provide all programmers with feedback indicating what
    parts of their programs are costing the most; indeed, this feedback
    should be supplied automatically unless it has been specifically
    turned off.

    人们误解了他的引言,经常暗示,当他的整个论文都在提倡微观优化时,微观优化还为时过早!他批评的一组人中,有一个认同他所说的"传统智慧",即总是忽视小企业的效率,他们经常滥用他的引言,这一引言最初部分是针对那些阻碍所有形式的微观优化的人的。

    然而,当一个经验丰富的手握住一个分析器时,这是一个有利于适当应用微优化的引用。今天类似的例子可能是,"人们不应该盲目地尝试优化他们的软件,但是当在关键领域应用自定义内存分配程序来提高引用的局部性时,会产生巨大的差异,"或者,"使用SOA代表的手写SIMD代码确实很难维护,而且不应该在整个PLA中使用它。"CE,但如果由经验丰富且有指导的手适当地应用,它可以更快地消耗内存。"

    每当你试图像Knuth在上面提到的那样推广谨慎应用的微优化时,最好加上一个免责声明来阻止新手过于兴奋和盲目地尝试优化,比如重写他们的整个软件来使用goto。这在一定程度上就是他所做的。他的话实际上是一个大免责声明的一部分,就像某人在一个燃烧的火坑上跳摩托车一样,可能会添加一个免责声明,即业余爱好者不应该在家里尝试,同时批评那些没有适当知识和设备而尝试却受伤的人。

    他认为"过早的优化"是由那些实际上不知道自己在做什么的人应用的优化:不知道是否真的需要优化,不使用适当的工具进行度量,可能不理解编译器或计算机体系结构的本质,最重要的是,"一文不值,一文不值",意思是他们忽略了优化(节省数百万美元)的巨大机会,试图节省一分钱,而所有这些都是在创建他们无法有效调试和维护的代码的同时进行的。

    如果你不适合于"微不足道和愚蠢的"类别,那么你就不会过早地按照Knuth的标准进行优化,即使你使用的是goto,以加速一个关键的循环(这对当今的优化程序来说不太可能有帮助,但如果它是这样的话,在一个真正关键的领域,那么你就不会过早地Y优化。如果你真的把你正在做的事情应用到那些真正需要的领域,而他们真正从中受益,那么你在Knuth看来做得很好。


    我不认为公认的最佳实践是过早的优化。更重要的是,根据使用场景的不同,哪些IFS可能存在性能问题,需要耗费大量的时间。一个很好的例子:如果你花了一周时间试图优化一个对象的反射,在你有证据证明它是一个瓶颈之前,你过早地优化了它。


    对我来说,过早的优化意味着在您拥有一个工作的系统之前,以及在您实际分析它并知道瓶颈在哪里之前,都要努力提高代码的效率。即使在这之后,在许多情况下,可读性和可维护性也应该先于优化。


    除非您发现由于用户或业务需要,您的应用程序需要更高的性能,否则几乎没有理由担心优化。即使这样,在分析完代码之前也不要做任何事情。然后攻击那些花费时间最多的部分。


    我认为,如果您在不知道在不同场景中可以获得多少性能的情况下优化某个对象,这是一种过早的优化。代码的目标应该是让人们更容易阅读。


    (P)5.As I posted on a similar question,the rules of optimation are:(p)(P)(1)Don't optimise(p)(P)(2)(for experts only)optimise later(p)(P)当选择过早?通常。(p)(P)The exception is perhaps in your design,or in well encapsulated code that is heavely used.In the past I've worked on some time critical code(an RSA implementation)where looking at the assembler that the compiler produced and removing a single unnecessary instruction in an inner loop gave a 30%speedup.但是,由于更具历史意义的算法被要求更为广泛。(p)(P)Another question to ask yourself when optiming is"am I doing the equivalent of optiming for a 300 baud modem here?"In other words,will Moore's law make your optimation remaint before too long.许多Scaling的问题只能通过在问题上敲打更多的硬件来解决。(p)(P)最后,但在方案拟订之前,还不能过早地作出选择。如果这是你在网上应用的话,你可以在贷款下运行它,看看这些机器人在哪里,但你喜欢的是,你将有相同的Scaling Problems as most other sites,和同样的解决办法将被采用。(p)(P)Edit:Incidently,regarding the linked article,I would question many of the assumptions made.Firstly it's not true that Moore's law stopped working in the 90s.Secondly,it's not obvious that user's time is more valuable than programmer's time.Most users are(to say the least)not frantically using every CPU cycle available anyhow,they are probably waiting for the network to do some thing.再加上,如果方案拟订的时间与执行某些方案的时间不同,就有机会节省费用,以便将一个百万美元的方案从用户手中拿走。任何比通常的选择办法更久的选择,都是建立在固定基础上。(p)