我的一位同事今天提交了一个名为ThreadLocalFormat的类,该类基本上将Java Format类的实例移动到了本地线程中,因为它们的创建不是线程安全的,而且“相对昂贵”。我写了一个快速测试,计算出我每秒可以创建200,000个实例,问他是否创建了那么多实例,对此他回答“远不及那么多”。他是一位出色的程序员,并且团队中的每个人都非常熟练,因此我们对所生成的代码毫无疑问,但这显然是在没有真正需要的地方进行优化的情况。他应我的要求取消了代码。你怎么看?这是“过早优化”的案例吗,真的有多糟糕?

评论

我认为您需要区分过早的优化和不必要的优化。对我而言,过早暗示“生命周期太早”,而不必要则暗示“不会增加重大价值”。国际海事组织,对后期优化的要求意味着次充好设计。

是的,但是邪恶是一个多项式并且有很多根,其中一些根很复杂。

您应该考虑,Knuth编写了这本1974年。在七十年代,编写慢速程序并不像现在这样容易。他在编写Pascal时就牢记于心,而不是Java或PHP。

不,所有邪恶的根源都是贪婪。

@ceving在70年代,编写慢速程序就像今天一样容易。如果选择错误的算法或错误的数据结构,则BAM!到处都有糟糕的表现。有人可能反驳。如今,这是更多的工具,并且程序员仍然必须编写遭受最基本的保存操作困扰的软件,这是不可原谅的。并行主义几乎成为一种商品,我们仍然受苦。不能将性能下降归咎于语言或工具,CPU或内存。这是众多事物之间的微妙平衡,这就是为什么几乎不可能尽早优化的原因。

#1 楼

重要的是要记住完整的引号:


我们应该忘记效率低的问题,大约有97%的时间是这样:过早的优化是万恶之源。但是,我们不应放弃这3%的临界机会。


这意味着,在没有可衡量的性能问题的情况下,您不应优化,因为您认为自己会获得性能提升。有明显的优化(例如,在紧密循环中不执行字符串连接),但在进行度量之前,应避免那些并非显而易见的优化。

“过早优化”的最大问题会带来意想不到的错误,并且会浪费大量时间。

评论


来自唐纳德·克努斯(Donald Knuth),如果他有证据支持我,我不会感到惊讶。 BTW,Src:《转到语句的结构化编程》,ACM Journal Computing Surveys,第6卷,第4期,1974年12月,第268页。 citeseerx.ist.psu.edu/viewdoc / ...

–mctylr
2010年3月1日于17:57

...一个好的程序员不会因这种推理而沾沾自喜,他应该明智地仔细看待关键代码;但只有在识别出该代码之后(其余的引号)

–mctylr
2010年3月1日17:59

我今天有一个2万名代表用户,我告诉我使用HashSet而不是List是过早的优化。有问题的用例是一个静态初始化的集合,其唯一目的是用作查找表。我认为在为工作选择正确的工具与过早的优化之间存在区别是没有错的。我认为您的帖子证实了这一理念:存在明显的优化...在进行衡量之前,应避免所有并非显而易见的优化。 HashSet的优化已被彻底测量和记录。

–暗恋
2014年1月23日14:33



@crush:是的:Set在语义上也比List更正确,更有意义,因此它不仅涉及优化方面。

–埃里克·卡普伦(Erik Kaplun)
2014年4月28日在2:38



我想补充一点,不要将过早的优化与将整个应用程序体系结构设计为总体上可以快速运行,可扩展并易于优化而混淆。

–埃里克·卡普伦(Erik Kaplun)
2014年4月28日在2:39



#2 楼

过早的微优化是万恶之源,因为微优化忽略了上下文。它们几乎永远不会表现出预期的行为。

按重要性顺序排列的一些早期优化是什么:


建筑优化(应用程序结构,方式它是组件化和分层的)
数据流优化(在应用程序的内部和外部)

一些中期开发周期优化:


数据结构,介绍具有更好性能或更低开销(如果需要)的新数据结构
算法(现在是开始在quicksort3和heapsort之间做出决定的好时机;-))

一些最终开发周期优化


查找代码热点(紧密循环,应该对其进行优化)
基于概要分析的代码计算部分的优化
现在可以像在代码优化中那样进行微优化了。应用程序的上下文及其影响可以正确衡量。

并非所有早期优化都是有害的,如果在错误的开发生命周期时间可能会对体系结构产生负面影响,可能对初始生产率产生负面影响,可能与性能无关,甚至在开发结束时由于不同的环境条件而产生不利影响。

如果您对性能感到关注(并且应该一直如此),请务必大放异彩。性能是一幅大图,与诸如以下内容无关:我应该使用int还是long?在使用性能时,请选择“自上而下”,而不是“自下而上”。

评论


约瑟夫·纽科默(Joseph M. Newcomer)撰写的“优化:您最糟糕的敌人”:flounder.com/optimization.htm

–罗恩·卢布
17年5月23日在21:50



#3 楼

没有先进行测量就进行优化几乎总是为时过早。

我相信在这种情况下是正确的,在一般情况下也是如此。

评论


这儿这儿!未经考虑的优化使代码无法维护,并且常常是导致性能问题的原因。例如您对程序进行多线程处理是因为您认为它可能会提高性能,但是,真正的解决方案应该是多个流程,而这些流程现在实施起来太复杂了。

–詹姆斯·安德森(James Anderson)
2012年5月2日下午5:01

除非有文件记载。

– nawfal
14年7月2日在13:18

是。完全同意。首先必须对其进行测量。在测试完所有内容并衡量每个步骤之前,您无法知道瓶颈在哪里。

–奥利弗·沃特金斯(Oliver Watkins)
2015年4月14日14:13在

测量可能会说谎。我已经看到经验丰富的专家花了数周时间阅读痕迹和运行配置文件,以撞墙,他们认为再也没有收获。然后,我阅读了整个代码,并在几个小时内进行了几处全面的更改,使性能提高了10倍。概要文件未显示热路径,因为整个代码设计不良。我还看到剖析器要求在不应有的路径上使用热路径。一个人“测量”本来可以优化热路径,但是他们应该意识到热路径是其他不良代码的症状。

–邦吉
18/09/13在18:15

#4 楼

如果优化导致以下原因,那么它就是“邪恶的”:


缺少清晰的代码
更多的代码
缺乏安全的代码
浪费了程序员的时间

在您的情况下,似乎已经花了一些程序员时间,代码并不太复杂(根据您的评论,团队中的每个人都可以理解),并且代码的未来更远证明(如果我理解您的描述,请立即安全使用)。听起来只有一点邪恶。 :)

评论


仅当成本(按项目要点计算)大于交付的摊销价值时。复杂性通常会带来价值,在这种情况下,人们可以封装价值,使其通过您的标准。它还可以重复使用,并继续提供更多价值。

– Shane MacLaughlin
08-10-17在10:36

前两点是我的主要观点,第四点是进行过早优化的负面结果。特别是,每当我看到有人从标准库中重新实现功能时,它都是一个危险信号。就像,我曾经看到有人为字符串操作实现自定义例程,因为他担心内置命令太慢。

–跳楼
2011年5月29日12:47



使代码线程安全不是优化。

–mattnz
2012年5月1日22:47

#5 楼

我对这个问题已有5年的历史感到惊讶,但没有人发表过Knuth要说的话,只有两句话。围绕着著名报价的几段内容对此进行了很好的解释。被引用的论文被称为“带语句的结构化编程”,虽然已有40年的历史了,但它既有争议,又有软件运动,它们都不复存在,并且有许多人从未使用过的编程语言示例。
这里引用了一个大笔的报价(摘自pdf第8页,原始268页):

示例2到示例2a仅占大约12%,许多人认为这无关紧要。当今许多软件工程师的共识是要求忽略小型服务器的效率。但是我相信这只是对他们认为精打细算的愚蠢程序员的过度反应,这些程序员无法调试或维护其“优化”程序。在既定的工程学科中,容易实现的12%的改善从未被视为微不足道;而且我相信在软件工程中应该有同样的观点。当然,我不会一劳永逸地进行这样的优化,但是当准备准备高质量的程序时,我不想将自己局限于那些拒绝我如此高效的工具。
没有怀疑效率的高低会导致滥用。程序员浪费大量时间来考虑或担心程序非关键部分的速度,而在考虑调试和维护时,这些提高效率的尝试实际上会产生严重的负面影响。我们应该忘掉效率低下的问题,大约有97%的时间是这样:过早的优化是万恶之源。
然而,我们不应该放弃那关键的3%的机会。一个好的程序员不会因这种推理而沾沾自喜,他应该明智地仔细看待关键代码。但只有在识别出该代码之后。对程序的哪些部分真正关键进行先验判断通常是一个错误,因为使用测量工具的程序员的普遍经验是他们的直觉猜测会失败。

另一个好处上一页的内容:

当然,在过去的十年中,根据时代的发展趋势,我自己的编程风格发生了变化(例如,我不再那么棘手了,我使用的次数更少了) ),但是我的风格的主要变化是由于这种内循环现象。现在,我非常紧张地注视着关键内部循环中的每个操作,试图修改我的程序和数据结构(如从示例1到示例2的更改一样),以便可以消除某些操作。这种方法的原因是:a)不需要很长时间,因为内部循环很短; b)回报是真实的; c)然后,我可以负担程序其他部分的效率降低,从而使它们更具可读性,更易于编写和调试。


#6 楼

我经常看到此引号用于证明明显的错误代码或那些虽然性能未得到衡量的代码,但很可能可以很容易地使其速度更快,而又不会增加代码大小或损害其可读性。

总的来说,我确实认为早期的微优化可能不是一个好主意。但是,宏优化(例如选择O(log N)算法而不是O(N ^ 2)之类的方法)通常是值得的,应该尽早完成,因为编写O(N ^ 2)算法可能很浪费,并且然后将其完全丢弃,以支持O(log N)方法。

请注意以下几句话:如果O(N ^ 2)算法简单易写,则可以将其丢弃如果事实证明它太慢的话,以后就不会感到内。但是,如果两种算法都同样复杂,或者预期的工作量如此之大,以至于您已经知道需要使用更快的算法,那么尽早进行优化是一项明智的工程决策,从长远来看,这将减少您的总工作量。

因此,总的来说,我认为正确的方法是在开始编写代码之前找出您的选择,并有意识地为您的情况选择最佳算法。最重要的是,“过早的优化是万恶之源”这句话不是无知的借口。职业开发人员应该大致了解一般的运营成本;他们应该知道,例如,


字符串比数字更昂贵
动态语言比静态类型的语言要慢得多
数组/向量的优点链表上的列表,反之亦然
何时使用哈希表,何时使用排序的映射以及何时使用堆(如果它们与移动设备一起使用)“ double”和“ int”在台式机上具有类似的性能(FP甚至可能更快),但在没有FPU的低端移动设备上,“两倍”的性能可能慢一百倍;
在Internet上传输数据的速度要比HDD访问速度慢,HDD的速度要比RAM慢得多,RAM的速度要比L1缓存和寄存器慢得多,并且互联网操作可能会无限期地阻塞(并且随时会失败)。

开发人员应该熟悉数据结构和算法的工具箱,以便他们可以轻松地使用正确的工具来完成工作。

拥有丰富的知识,而个人工具箱则使您可以轻松地进行优化。付出不必要的优化工作是很邪恶的(我承认不止一次陷入这种陷阱)。但是,当优化就像选择一个set / hashtable而不是一个数组一样简单,或者将一个数字列表存储在double []而不是string []中时,为什么呢?我不确定我在这里与Knuth意见不同,但是我认为他是在谈论低级优化,而我在谈论高级优化。

请记住,这句话最初来自1974年。1974年,计算机发展缓慢,计算能力昂贵,这使得一些开发人员倾向于逐行过度优化。我认为这就是Knuth所反对的。他并不是说“完全不用担心性能”,因为在1974年那将是一次疯狂的演讲。 Knuth在解释如何优化。简而言之,应该只关注瓶颈,在执行此操作之前,必须执行测量以找到瓶颈。

请注意,只有编写了要测量的程序,才能找到瓶颈,这意味着必须在做出任何衡量指标之前就做出一些性能决定。如果您弄错了这些决定,有时很难更改。因此,最好对花费多少有一个总体的了解,以便在没有可用的硬数据时可以做出合理的决定。

优化的时间提早以及对性能的担忧程度取决于工作。编写只运行几次的脚本时,根本不担心性能通常会完全浪费时间。但是,如果您为Microsoft或Oracle工作,并且正在使用一个库,其他成千上万的开发人员将以成千上万的不同方式使用该库,则可能需要对其进行优化,以便涵盖所有不同类型的库。用例有效。即使如此,对性能的需求也必须始终与对可读性,可维护性,美观性,可扩展性等的需求相平衡。

评论


阿们这些天来,试图证明使用错误工具进行工作的人过早地抛出过早的优化。如果您提前知道适合该工作的工具,那么没有理由不使用它。

–暗恋
2014年1月23日14:42

#7 楼

就个人而言,如前一个线程所述,我认为在您会遇到性能问题的情况下,过早的优化并不很糟糕。例如,我编写表面建模和分析软件,在那里我经常处理数千万个实体。在设计阶段规划最佳性能要比对弱设计进行后期优化要好得多。

要考虑的另一件事是您的应用程序将来如何扩展。如果您认为您的代码寿命长,那么在设计阶段优化性能也是一个好主意。

以我的经验,后期优化可提供高昂的回报。在设计阶段通过算法选择和调整进行优化会更好。依靠分析器来了解代码的工作方式并不是获取高性能代码的好方法,您应该事先了解这一点。

评论


这当然是正确的。我想过早的优化是指使代码变得更复杂/难以理解以获得不明确的好处时,这种方式仅具有局部影响(设计具有全局影响)。

–Paul de Vrieze
08-10-17在10:12

这都是关于定义的。我将优化视为设计和编写代码以使其以最佳方式执行。一旦发现代码不够快速或不够高效,这里的大多数人似乎就将其视为乱搞代码。我通常在设计过程中花费大量时间进行优化。

– Shane MacLaughlin
08-10-17在10:27

首先优化设计,最后优化代码。

–BCS
08年12月19日在20:58

您的情况是非常正确的,但是对于大多数程序员而言,他们认为它们会遇到性能问题,但实际上他们从来不会这样做。当处理1000个实体时,许多人担心性能,而对数据的基本测试表明,在达到1000000个实体之前,性能还不错。

–托比·艾伦(Toby Allen)
2013年1月26日15:32

“在设计阶段进行最佳性能的计划要远远优于后期对弱设计的优化”,而“后期优化则以高价提供了微薄的回报”!对于生产的所有系统中的97%,可能不是正确的,但对于许多(令人惊讶的是)系统而言,情况并非如此。

– Olof Forshell
2015年3月10日在12:33

#8 楼

实际上,我了解到过早的非优化通常是万恶之源。

当人们编写软件时,它最初会出现问题,例如不稳定,功能受限,可用性差和性能差。当软件成熟时,所有这些通常都会得到修复。

所有这些,除了性能。似乎没有人关心性能。原因很简单:如果软件崩溃,则可以修复该漏洞,仅此而已;如果缺少某个功能,则可以实施并完成;如果该软件的性能不佳,多数情况下并不是由于缺少微优化而引起的,而是由于设计不良,没有人会去接触软件的设计。永远。

看看Bochs。它慢得要命。它会变得更快吗?也许可以,但仅在百分之几的范围内。它永远不会获得与VMWare,VBox甚至QEMU等虚拟化软件相媲美的性能。因为它在设计上很慢!

如果软件的问题是它很慢,那么就因为它非常慢而只能通过大量提高性能来解决。 + 10%根本不会使速度较慢的软件变快。通常,以后的优化不会使您获得超过10%的收益。

因此,如果性能对您的软件非常重要,则应在设计之初就考虑到这一点,而不是思考“哦,是的,它很慢,但是我们以后可以改善它”。因为您做不到!

我知道这真的不适合您的具体情况,但是它回答了一个普遍的问题:“过早的优化真的是万恶之源吗?” -具有清晰的编号。

每个优化(如任何功能等)都必须精心设计和仔细实施。这包括对成本和收益的适当评估。当算法无法带来可衡量的性能提升时,请勿优化算法以节省一些时间。

举个例子:您可以通过内联函数来提高功能的性能,可能节省一些周期,但是与此同时,您可能会增加可执行文件的大小,增加TLB和缓存未命中的机会,这会花费数千个周期,甚至分页操作,这将完全破坏性能。如果您不了解这些内容,那么您的“优化”可能会变得很糟糕。

愚蠢的优化比“过早的”优化更邪恶,但是两者都比过早的非优化好。
/>

#9 楼

PO有两个问题:首先,开发时间用于不必要的工作,可用于编写更多功能或修复更多错误,其次,错误的安全意识是代码正在有效运行。 PO通常会涉及优化代码,而这不会成为瓶颈,而不会引起注意。 “过早的”位表示在使用适当的测量方法确定问题之前就已经完成了优化。

所以基本上,是的,这听起来像过早的优化,但是除非得到它,否则我不一定会取消优化引入错误-毕竟已经对其进行了优化(!)

评论


您的意思是说“编写更多测试”而不是“编写更多功能”,对吗? :)

– Greg Hewgill
08-10-17在8:42

更多功能需要更多测试:)

– workmad3
08-10-17在8:51

嗯是的我就是这个意思

– Harriyott
08-10-17在9:40

该代码进一步引入了复杂性,可能不会被普遍使用。对其进行备份(以及类似操作)可使代码保持干净。

–Paul de Vrieze
08-10-17在10:07

#10 楼

我相信这就是Mike Cohn所说的“镀金”代码,即花时间在可能不错但不必要的事情上。

他建议不要这样做。

聚苯乙烯“镀金”可能是特定功能的千篇一律。当您查看代码时,它会采取不必要的优化,“面向未来”的类等形式。

评论


我认为“镀金”与优化不同。优化通常是为了获得最大的性能,而“镀金”则是添加对产品而言并不关键但看起来/感觉很酷的“钟声”(所有额外的功能)。

–斯科特·多曼(Scott Dorman)
08-10-17在9:16

#11 楼

由于理解代码没有问题,因此可以将这种情况视为例外。

但是,一般而言,优化导致代码的可读性和可读性降低,因此仅应在必要时应用。一个简单的示例-如果您只需要对几个元素进行排序-那么可以使用BubbleSort。但是,如果您怀疑元素会增加而又不知道有多少,那么使用QuickSort(例如)进行优化并不是邪恶的,而是必须的。在设计程序时应考虑这一点。

评论


不同意我会说永远不要使用冒泡排序。 Quicksort已成为事实上的标准,并且已广为人知,并且在所有情况下都像气泡排序一样容易实现。最低的公分母不再那么低;)

– Shane MacLaughlin
08-10-17在8:47

对于数量很少的项目,快速排序所需的递归可以使其比像样的冒泡排序慢一些……更不用说冒泡排序在最坏情况下的快速排序(即对排序列表进行快速排序)中更快。

– workmad3
08-10-17在8:53

是的,但这仅是如何选择满足不同需求的算法的示例;)

– m_pGladiator
08-10-17在8:54

是的,但是我会将quicksort作为默认排序。如果我认为Bubblesort可以提高性能,那将是一种优化,而不是相反。我选择quicksort作为默认设置,因为它被很好地理解并且通常更好。

– Shane MacLaughlin
08-10-17在9:15

我对默认排序的想法是库给我的(qsort()、. sort(),(sort ...),等等)。

– David Thornley
08年12月19日在21:25

#12 楼

我发现过早优化的问题主要发生在重新编写现有代码以使其更快时。我可以看到首先编写一些复杂的优化是一个问题,但是大多数情况下,我看到过早的优化会抬起它的丑陋头来修复(没有)已知的问题。

最糟糕的例子是,每当我看到有人重新实现标准库中的功能时,就会出现这种情况。那是一个重大的危险信号。就像,我曾经看到某人实现了用于字符串操作的自定义例程,因为他担心内置命令太慢。

这导致代码更难以理解(不好)并且消耗大量代码在工作上花费的时间可能没什么用(不好)。

#13 楼

从不同的角度来看,据我的经验,大多数程序员/开发人员都不打算成功,“原型”几乎总是成为1.0版。我对4种独立的原始产品有第一手的经验,其中优雅,性感和功能强大的前端(基本上是UI)导致了广泛的用户采用和热情。在每种产品中,性能问题都在相对较短的时间内(1-2年)开始蔓延,特别是随着更大,要求更高的客户开始采用该产品。尽管新功能开发在管理层的优先级列表中占主导地位,但是性能很快就在问题列表中占主导地位。随着每个版本添加听起来不错但由于性能问题而几乎无法访问的新功能,客户变得越来越沮丧。

因此,非常基本的设计和实现缺陷在“原型”成为产品(和公司)长期成功的主要绊脚石。

您的客户演示在使用XML DOM,SQL Express和许多产品的笔记本电脑上可能看起来并表现出色客户端缓存的数据。如果您成功的话,生产系统可能会崩溃。

在1976年,我们仍在讨论计算平方根或对大型数组进行排序的最佳方法,而Don Knuth的格言是针对错误的。专注于在设计过程中尽早优化这类低级例程,而不是专注于解决问题然后优化代码的局部区域。

当有人重复这句话作为不编写高效代码的借口时(C ++,VB,T-SQL或其他),或者由于没有正确设计数据存储区,或者由于没有考虑网络体系结构,那么IMO只是在对我们的工作的真实性质进行了非常浅薄的理解。 />雷

评论


哈哈,或者当具有三个用户的演示版成为具有1000个版本的1.0版时。

– Olof Forshell
2015年10月6日,12:36

#14 楼

我想这取决于您如何定义“过早”。在编写时快速地执行低级功能并不是天生的邪恶。我认为这是对报价的误解。有时我认为报价可能会带来更多限制。我还是回应了m_pGladiator关于可读性的评论。

#15 楼

答案是:这取决于。我会说,对于某些类型的工作,例如复杂的数据库查询,效率至关重要。在许多其他情况下,计算机将大部分时间都花在等待用户输入上,因此优化大多数代码充其量是努力的余地,最不利的是适得其反。

在某些情况下,您可以为提高效率或性能而设计(感知的或真实的)-选择适当的算法或设计用户界面,以便在后台进行某些昂贵的操作。在许多情况下,进行概要分析或其他确定热点的操作将为您带来10/90的收益。

我可以描述的一个例子是我曾经为一个法院案件管理系统创建的数据模型,其中包含大约560个表。它最初是经过规范化的(如某大五家公司的顾问所说的是“美丽规范化”),我们只需要在其中放入四项非规范化数据:


视图以支持搜索屏幕
一个触发器维护的表可以支持用物化视图无法完成的另一个搜索屏幕。
一个非规范化报告表(之所以存在,是因为我们必须承担一定的吞吐量报告何时对数据仓库项目进行固定)
用于接口的一个触发器维护表,该表必须搜索系统中大量不同事件中的最新事件。

此是(当时)澳大利亚最大的J2EE项目-超过100年的开发时间-并且它在数据库模式中有4个非规范化项目,其中之一实际上根本不属于该项目。

#16 楼

可以肯定的是,过早的优化并不是万恶之源。但是它有缺点:


您在开发过程中投入了更多的时间
您投入了更多的时间对其进行测试
您花费了更多的时间来修复本来不会存在的错误在那里

可以进行早期可见性测试,而不是进行过早的优化,以查看是否确实需要更好的优化。

#17 楼

坚持“ PMO”(即部分引号)的大多数人都说,优化必须基于测量,并且直到最后才能进行测量。

这也是我的经验在大型系统开发中,随着开发接近完成,性能测试将在最后完成。

如果我们遵循这些人的“建议”,那么所有系统的运行速度将非常缓慢。它们也很昂贵,因为它们的硬件需求比最初设想的要大得多。

我一直提倡在开发过程中定期进行性能测试:它将同时表明新代码的存在(



可以将新实现的代码的性能与现有的类似代码的性能进行比较。随着时间的流逝,将为新代码的性能建立一个“感觉”。
如果现有代码突然陷入困境,您就会了解到发生了什么
,您可以立即对其进行调查,而不是(
以后会影响整个系统。

另一个有趣的想法是在功能块级别上对软件进行检测。系统执行时,它会收集有关功能块执行时间的信息。当执行系统升级时,可以确定哪些功能块执行的功能与早期版本中的功能相同,哪些功能块已退化。在软件的屏幕上,可以从帮助菜单访问性能数据。

查看关于PMO可能意味着什么或不意味着什么的出色文章。