87

正如高德纳所说,

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

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

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

这是 Randall Hyde 的一篇相关文章,名为The Fallacy of Premature Optimization

4

20 回答 20

113

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

以优化的名义引入的某些成语已经变得如此流行,以至于每个人都理解它们并且它们已经成为预期,而不是为时过早。例子包括

  • 在 C 中使用指针算术而不是数组表示法,包括使用这样的习语

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

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

除了这些习语,走捷径后果自负

所有优化都为时过早,除非

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

  • 你有一个测量(配置文件或类似的)表明优化可以改善事情

(也可以针对内存进行优化。)

直接回答问题:

  • 如果您的“不同”技术使程序更难理解,那么它就是过早的优化

编辑:作为对评论的回应,使用快速排序而不是像插入排序这样更简单的算法是每个人都理解和期望的习语的另一个例子。(尽管如果您编写自己的排序例程而不是使用库排序例程,希望您有一个很好的理由。)

于 2008-12-22T04:06:56.453 回答
42

恕我直言,90% 的优化应该发生在设计阶段,基于感知的当前,更重要的是,未来的需求。如果您因为应用程序无法扩展到所需的负载而不得不取出分析器,那么您已经太晚了,IMO 将浪费大量时间和精力,而无法纠正问题。

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

所以我的建议是,尽早优化,根据您的要求,而不是您的代码,并着眼于您的应用程序可能的延长寿命。

于 2008-12-22T09:06:01.747 回答
34

如果你还没有分析过,那还为时过早。

于 2008-12-22T05:13:15.040 回答
28

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

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

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

于 2008-12-22T04:02:10.020 回答
11

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

说和做之间有脱节。

我做了很多性能调优,从原本设计良好的代码中挤出大量因素,似乎没有过早优化。 这是一个例子。

在几乎所有情况下,性能欠佳的原因都是我所说的泛泛性,即使用抽象的多层类和彻底的面向对象设计,其中简单的概念不太优雅,但完全足够了。

而在讲授这些抽象设计概念的教材中,比如通知驱动的架构、信息隐藏等,只要简单地设置一个对象的布尔属性就可以产生无限的活动连锁反应,给出的理由是什么?效率

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

于 2009-11-23T22:27:53.930 回答
9

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

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

于 2008-12-22T09:46:34.690 回答
8

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

于 2008-12-22T16:20:21.340 回答
7

您似乎在谈论的是优化,例如使用基于散列的查找容器与索引容器(如数组),此时将完成大量键查找。这不是过早的优化,而是您应该在设计阶段决定的事情。

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

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

回应在这个问题上发布的许多其他评论:算法选择!=优化

于 2008-12-22T13:04:07.553 回答
6

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

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

于 2008-12-22T04:05:36.987 回答
4

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

我对过早优化的定义是“将精力浪费在未知的性能问题上”。绝对有优化的时间和地点。但是,诀窍是仅在对应用程序性能重要且额外成本超过性能损失的地方花费额外成本。

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

我的建议:尝试仅在可以量化收益时才支付优化成本。

于 2008-12-22T05:43:54.400 回答
4

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

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

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

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

于 2008-12-22T09:17:13.707 回答
4

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

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

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

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

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

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

挑剔是使用除法表达式还是移位表达式不一定是过早的优化。如果你没有首先优化架构、数据结构、算法、内存占用和流量控制,那还为时过早。

当然,如果您不定义目标性能阈值,任何优化都为时过早。

在大多数情况下,要么:

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

或者

B) 即使在执行了所有可能的优化之后,您也不会达到您的目标性能阈值,并且低级优化在性能上没有产生足够的差异来证明可读性的损失是合理的。

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

于 2008-12-24T21:07:12.617 回答
3

诺曼的回答非常好。不知何故,你经常做一些“过早的优化”,实际上是最佳实践,因为众所周知,这样做是完全低效的。

例如,要添加到 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++)并且仍然可读,所以没问题。

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

于 2008-12-22T08:08:09.023 回答
3

对于极端情况,应该保留使用分析器的需要。项目的工程师应该知道性能瓶颈在哪里。

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

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

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

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

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

于 2008-12-22T13:14:46.893 回答
2

值得注意的是,Knuth 的原始引述来自他撰写的一篇论文,该论文提倡goto在精心挑选和测量的区域中使用作为消除热点的一种方式。goto他的引用是他添加的一个警告,以证明他使用以加速这些关键循环的理由。

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

并继续:

当今许多软件工程师所共有的传统智慧要求忽略小规模的效率。但我相信这只是对他们看到的一分钱一磅的愚蠢程序员所实施的滥用行为的过度反应,他们无法调试或维护他们的“优化”程序。在已建立的工程学科中,12% 的改进很容易获得,但绝不会被认为是微不足道的;我相信同样的观点应该在软件工程中占上风。当然,我不会费心在一次性工作上进行这样的优化,但是当涉及到准备质量程序的问题时,我不想将自己限制在拒绝我这种效率的工具[即,goto 在这种情况下的陈述]。

请记住他如何在引号中使用“优化”(该软件可能实际上效率不高)。另请注意,他不仅批评了这些“一分钱一分货”的程序员,还批评了那些建议您始终忽略小的低效率的人。最后,到经常被引用的部分:

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

...然后更多地了解分析工具的重要性:

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

人们到处都在滥用他的引用,当他的整篇论文都在提倡微优化时,经常暗示微优化还为时过早!他批评的一群人回应了这种“传统智慧”,因为他总是忽略小事的效率,他们经常滥用他的引言,该引述最初是针对那些不鼓励所有形式的微优化的类型的人。 .

然而,当经验丰富的手持分析器使用时,它是支持适当应用微优化的引用。今天的类比等价物可能是,“人们不应该盲目地优化他们的软件,但是自定义内存分配器在应用于关键领域以提高引用的局部性时会产生巨大的影响”,或者,“使用SoA rep 真的很难维护,你不应该到处使用它,但是如果由有经验和有指导的人适当地应用,它会更快地消耗内存。

每当您尝试像 Knuth 上面所宣传的那样推广精心应用的微优化时,最好加入免责声明,以阻止新手过于兴奋和盲目地尝试优化,例如重写他们的整个软件以使用goto. 这部分是他正在做的。他的引述实际上是一个大免责声明的一部分,就像骑摩托车跳过燃烧的火坑的人可能会添加一个免责声明,即业余爱好者不应该在家里尝试这个,同时批评那些在没有适当知识和设备的情况下尝试并受伤的人.

他认为“过早的优化”是那些实际上不知道自己在做什么的人所应用的优化:不知道优化是否真的需要,没有使用适当的工具进行衡量,也许不了解优化的本质他们的编译器或计算机架构,最重要的是,是“一分钱一磅的傻瓜”,这意味着他们忽略了优化(节省数百万美元)的大好机会,试图捏捏便士,而在创建代码的同时,他们无法做到更有效地调试和维护。

如果您不属于“一分钱一磅的愚蠢”类别,那么即使您使用 agoto来加速关键循环(这不太可能) ,您也不会过早按照 Knuth 的标准进行优化对当今的优化器有很大帮助,但如果确实如此,并且在真正关键的领域,那么您就不会过早地进行优化)。如果您实际上将您正在做的任何事情应用到真正需要的领域并且他们真正从中受益,那么您在 Knuth 眼中就做得很好。

于 2017-12-22T04:52:01.507 回答
1

对我来说,过早的优化意味着在你拥有一个工作系统之前,在你真正分析它并知道瓶颈在哪里之前,尝试提高你的代码效率。即使在那之后,在许多情况下,可读性和可维护性也应该先于优化。

于 2008-12-22T04:06:38.933 回答
1

我不认为公认的最佳实践是过早的优化。更多的是关于根据使用场景在潜在性能问题的假设上花费时间。一个很好的例子:如果你花了一周时间试图优化一个对象的反射,在你证明它是一个瓶颈之前你过早地优化了。

于 2008-12-22T04:36:32.260 回答
1

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

于 2008-12-22T05:50:56.210 回答
0

我的看法是,如果您在不知道在不同情况下可以获得多少性能的情况下优化某些东西,那么这就是过早的优化。代码的目标应该是让人类更容易阅读。

于 2008-12-22T05:37:24.007 回答
0

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

1)不要优化

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

什么时候优化还为时过早?通常。

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

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

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

编辑:顺便说一句,关于链接的文章,我会质疑所做的许多假设。首先,摩尔定律在 90 年代停止工作是不正确的。其次,用户的时间比程序员的时间更有价值这一点并不明显。大多数用户(至少可以说)无论如何都不会疯狂地使用每个可用的 CPU 周期,他们可能正在等待网络做某事。另外,当程序员的时间从实现其他事情上转移到用户打电话时程序所做的事情上,这会产生机会成本。任何比这更长的时间通常都不是优化,而是错误修复。

于 2008-12-22T11:43:22.367 回答