优化过早是什么时候?

91
正如Knuth所说,
“我们应该忘记小的效率问题,大约97%的时间:过早优化是万恶之源。”
这是在Stack Overflow回答问题时经常出现的情况,比如“哪种循环机制最高效”,“SQL优化技巧?”(等等)。对于这些优化提示问题的标准答案是先对代码进行分析,看看是否存在问题,如果没有问题,那么你的新技术就是不必要的。
我的问题是,如果一种特定的技术不同但并不特别晦涩或隐晦,那真的可以被认为是过早优化吗?
这里有一篇相关文章,由Randall Hyde撰写,名为过早优化的谬论

46
很讽刺的是,许多人高喊着“过早优化是万恶之源”,却自己过早地优化了这句话。 - some
25
"我们应该忘记那些小的效率优化,大约97%的时间都是这样:过早地进行优化是万恶之源。然而,在关键的3%的情况下,我们不应放弃机会。"(唐纳德·克努斯) - some
2
我相信是CA Hoare说过这句话。甚至Knuth也这么说。 - jamesh
1
是的,Tony Hoare 最初说了“过早优化是万恶之源”的一部分,但 Knuth 引用/改编了他的话,并加上了后面的内容。 - nickf
2
虽然我同意这个引用经常被滥用和断章取义,但从定义上来说,它总是正确的,因为存在“过早优化”(但通常被错误地用作懒散设计和代码的辩解)。从定义上来说,如果优化发生在开发的最适时点,无论是在设计期还是其他任何时候,它都不是“过早”的。 - Lawrence Dol
显示剩余7条评论
20个回答

4

我尽量只在确认存在性能问题时才进行优化。

我对过早优化的定义是“在代码未被证明存在性能问题时浪费精力进行的优化”。当然,优化有其适用的时间和地点。然而,关键在于只在应用程序性能需要并且额外成本超过性能损失的情况下才花费额外成本。

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

我的建议:只有在您能够量化收益时才支付优化成本。


4
在编程时,一些参数是至关重要的,其中包括:
  • 可读性
  • 可维护性
  • 复杂度
  • 健壮性
  • 正确性
  • 性能
  • 开发时间
优化(追求性能)通常是以其他参数为代价的,并且必须权衡这些方面的“损失”。
当您有选择使用表现良好的知名算法的选项时,预先进行“优化”的成本通常是可以接受的。

1
您在上述列表中缺少了最重要的QA参数:满足需求。如果软件不符合预期受众的要求,所有其他参数都毫无意义。如果性能不可接受,则未达到要求。 - SmacL
3
可以说这被正确性所覆盖。此外,“性能”在“尽可能快”的意义上很少成为要求,即使Ola认为这是与其他需求权衡的一点仍然是正确的。 - frankodwyer

3

Norman的回答非常好。有些“过早优化”实际上是最佳实践,因为否则将完全低效。

例如,补充一下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 ++)更快,并且仍然可读,所以可以采用。

诸如此类的微小优化永远不应以代码的可读性为代价。


3

只有在极端情况下才需要使用分析器。项目的工程师应该知道性能瓶颈出现在哪里。

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

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

重新设计比从一开始就以明显的方式优化设计更加昂贵。

显然,第一次可能会错过一些小事情,但这些很少是关键的设计决策。

因此:我认为不优化设计本身就是代码异味。


问题在于瓶颈通常出现在您从未想过会成为问题的代码部分。性能分析消除了虚假和炫耀,并显示程序的实际成本中心。最好从一开始就正确处理明显的事情,但对于其他所有事情,都可以使用性能分析。 - Chris Smith

3
值得注意的是,Knuth最初的引用来自他撰写的一篇论文,他在其中提倡在精心选择和测量的领域中使用goto作为消除热点的一种方式。他的引用是一个警告,他添加了这个理由来证明他使用goto加速关键循环的合理性。
引用如下:
"[...] 再说一遍,如果n的平均值约为20,并且在程序中执行搜索例程大约一百万次左右,则总体运行速度可以明显提高。这样的循环优化[使用goto]并不难学习,正如我所说,它们只适用于程序的一小部分,但通常会带来可观的节省。[...]"
他接着说:
许多当今软件工程师共享的传统智慧呼吁忽略小规模的效率问题;但我认为这只是对那些小聪明却糊涂的程序员(他们不能调试或维护其“优化”程序)所看到的滥用的过度反应。在已经建立的工程学科中,即使是12%的改进也很重要,而且很容易实现;我认为在软件工程中也应该有同样的观点。当然,对于一次性的工作,我不会费心进行这样的优化,但是当涉及到准备高质量的程序时,我不想限制自己只使用拒绝这些效率的工具[即在此上下文中使用goto语句]。
请记住他在引号中使用了“优化”(软件可能实际上并不高效)。还要注意,他不仅批评了这些“小聪明却糊涂”的程序员,还批评了那些建议您始终忽略小型低效率的人。最后,他经常引用以下部分:
毫无疑问,效率的圣杯会导致滥用。程序员浪费了大量时间思考或担心其程序的非关键部分的速度,而这些效率尝试在考虑调试和维护时实际上会产生强烈的负面影响。我们应该忘记小效率,例如97%的时间;过早的优化是万恶之源。
...然后是关于性能分析工具重要性的更多内容:
在程序中,事先判断哪些部分真正关键通常是错误的,因为使用测量工具的程序员的普遍经验是他们的直觉猜测是失败的。在使用这些工具七年后,我已经确信,从现在开始编写的所有编译器都应该被设计为向所有程序员提供反馈,指示他们程序中哪些部分成本最高;事实上,除非明确关闭,否则应自动提供此反馈。
人们在很多地方误用了他的引用,经常暗示微观优化是过早的,而他整篇论文都在倡导微观优化!他批评的其中一群人在这里回应了他所说的“常识”,即总是忽略小规模效率的那些类型,他们经常误用他的引语,而原本是针对这些类型的,这些类型会阻挠所有形式的微观优化。
然而,这是一个支持适当应用微观优化的引语,只有在有经验的手持有分析器的情况下使用。今天的类比相当于,“人们不应该盲目地尝试优化他们的软件,但是自定义内存分配器可以在关键领域应用以提高参考局部性”,或者,“手写的SIMD代码使用SoA rep真的很难维护,并且你不应该到处使用它,但是如果由有经验和指导的人适当应用,则可以更快地消耗内存。”
任何时候,当你像Knuth以上所推广的谨慎应用微观优化时,最好加上免责声明,以防止新手过度兴奋并盲目地尝试优化,例如重写他们的整个软件以使用goto。这在某种程度上就是他所做的。他的引语实际上是一个大免责声明的一部分,就像某个人在火坑上进行摩托车跳跃时可能会添加一个声明,警告业余爱好者不要在家里尝试这样做,同时批评那些没有适当知识和设备而尝试并受伤的人。
他所谓的“过早优化”是由那些实际上不知道自己在做什么的人应用的优化:不知道是否真正需要优化,没有使用正确的工具进行测量,也许不理解编译器或计算机体系结构的本质,并且最重要的是,“眼高手低”,意味着他们忽视了大机会去优化(节省数百万美元),试图捏紧骨头,而同时创建了他们无法有效调试和维护的代码。
如果您不符合“眼高手低”的类别,那么按照Knuth的标准,即使您在关键循环中使用goto以加速,也不算是过早优化(这在今天的优化器中不太可能起到很大的作用,但如果确实如此,并且在真正关键的领域中得到真正的好处,则您不会是过早优化)。如果您实际上正在将您所做的任何事情应用于真正需要它们并且从中真正受益的领域,则在Knuth的眼中,您正在做得非常好。

1

对我而言,过早地进行优化意味着在你拥有一个可工作的系统之前,试图提高代码的效率,并且在你实际上对其进行分析并知道瓶颈在哪里之前。即使在此之后,在许多情况下,可读性和可维护性也应该优先于优化。


1

我认为被广泛认可的最佳实践不是过早优化。这更多地涉及到在可能存在性能问题的使用场景中浪费时间研究“如果”条件。一个很好的例子是:如果你在没有证据表明反射操作是一个瓶颈之前,花费一周时间优化反射操作,那么你就是过早地进行了优化。


1

除非你发现你的应用程序需要更高的性能,这可能是由于用户或业务需求,否则没有必要担心优化。即使那样,也不要在对代码进行分析之前就开始做任何事情。然后攻击花费最多时间的部分。


1

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

1)不要优化

2)(仅限专家)稍后再进行优化

什么时候进行优化过早?通常情况下。

例外情况可能出现在您的设计中,或者在使用频率很高的封装良好的代码中。过去,我曾经处理过一些时间关键的代码(RSA实现),在查看编译器生成的汇编代码并删除内部循环中的单个不必要指令后,速度提升了30%。但是,使用更复杂的算法带来的加速比这还要高几个数量级。

另一个在优化时要问自己的问题是“我是否在进行等同于为300波特调制解调器进行优化?”换句话说,在摩尔定律使您的优化变得无关紧要之前,您的优化是否仍然有效。许多扩展问题可以通过投入更多硬件来解决。

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

编辑:顺便提一下,关于链接的文章,我对其中许多假设表示怀疑。首先,摩尔定律在90年代停止工作并不是真的。其次,用户的时间比程序员的时间更有价值并不明显。大多数用户(至少可以这么说)并没有疯狂地使用每个可用的CPU周期,他们可能正在等待网络完成某些操作。此外,当程序员的时间从实现其他功能转移到削减程序在用户通话时执行的某些操作的几毫秒时,就会存在机会成本。超过这个时间的任何优化通常都不是优化,而是修复错误。


-1
我认为,如果你在不知道在不同情况下可以获得多少性能的情况下进行优化,那么这是一种过早的优化。代码的目标应该是让人类阅读最容易。

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