发布评论
评论(20)
Don Knuth 发起了文学编程运动,因为他相信最重要的是计算机代码的功能是将程序员的意图传达给人类读者。 任何以性能的名义使代码更难以理解的编码实践都是不成熟的优化。
某些以优化名义引入的惯用语已经变得如此流行,以至于每个人都理解它们,并且它们已经成为预期,而不是为时过早。 示例包括
在 C 中使用指针算术而不是数组表示法,包括使用诸如
for (p = q; p < lim; p++)
将全局变量重新绑定到局部变量 之类的习惯用法,如
本地表、io、字符串、数学 = 表、io、字符串、数学
超越此类惯用法,走捷径后果自负。
所有优化都是不成熟的,除非
程序太慢(很多人忘记了这部分)。
您有一个衡量(个人资料或类似内容),表明优化可以改善情况。
(也可以针对内存进行优化。)
直接回答问题:
- 如果您的“不同”技术使程序更难以理解,那么这是一个过早的优化。
编辑:针对评论,使用快速排序而不是插入排序等更简单的算法是每个人都理解和期望的习语的另一个例子。 (尽管如果您编写自己的排序例程而不是使用库排序例程,人们希望您有一个非常充分的理由。)
从数据库的角度来看,在设计阶段不考虑优化设计充其量是鲁莽。 数据库不容易重构。 一旦它们设计得很糟糕(这就是不考虑优化的设计,无论你如何试图隐藏在过早优化的废话背后),几乎永远无法从中恢复,因为数据库对于数据库来说太基础了整个系统的运行。 考虑到您期望的情况的最佳代码来正确设计,比等到有一百万个用户并且人们因为您在整个应用程序中使用光标而尖叫时要便宜得多。 其他优化,例如使用可控制代码、选择看起来最好的索引等,只有在设计时才有意义。 之所以称之为快速和肮脏是有原因的。 因为它永远无法很好地工作,所以不要用快速性来代替好的代码。 另外坦率地说,当您了解数据库中的性能调优时,您可以在相同或更少的时间内编写更有可能性能良好的代码,而不是编写性能不佳的代码。 不花时间学习什么是性能良好的数据库设计是开发人员的懒惰,而不是最佳实践。
您似乎在谈论的是优化,例如在完成大量键查找时使用基于散列的查找容器与索引容器(例如数组)。 这不是过早的优化,而是您应该在设计阶段决定的事情。
Knuth 规则涉及的优化类型是最小化最常见代码路径的长度,通过例如在汇编中重写或简化代码来优化运行最多的代码,使其不那么通用。 但是,除非您确定代码的哪些部分需要这种优化,并且优化将(可能?)使代码更难以理解或维护,否则这样做是没有用的,因此“过早的优化是万恶之源”。
Knuth 还表示,改变程序使用的算法以及解决问题的方法总是比优化更好。 例如,稍微调整一下就可以通过优化使速度提高 10%,而从根本上改变程序的工作方式可能会使速度提高 10 倍。
针对此问题上发布的许多其他评论:算法选择!=优化
优化可以在不同的粒度级别上进行,从非常高的级别到非常低的级别:
从良好的架构、松散耦合、模块化等开始。
为问题选择正确的数据结构和算法。
优化内存,尝试在缓存中容纳更多代码/数据。 内存子系统比 CPU 慢 10 到 100 倍,如果将数据分页到磁盘,则速度会慢 1000 到 10,000 倍。 与优化单个指令相比,对内存消耗保持谨慎更有可能带来重大收益。
在每个函数中,适当使用流程控制语句。 (将不可变表达式移出循环体。将最常见的值放在 switch/case 等中)
在每个语句中,使用产生正确结果的最有效的表达式。 (乘法与移位等)
对是否使用除法表达式或移位表达式进行挑剔不一定是过早优化。 如果您没有首先优化架构、数据结构、算法、内存占用和流控制,那么这样做还为时过早。
当然,如果您没有定义目标性能阈值,任何优化都是不成熟的。
在大多数情况下,要么:
A) 您可以通过执行高级优化来达到目标性能阈值,因此无需摆弄表达式。
或
B)即使在执行所有可能的优化之后,您也不会达到目标性能阈值,并且低级优化不会在性能上产生足够的差异来证明可读性损失是合理的。
根据我的经验,大多数优化问题都可以在架构/设计或数据结构/算法级别解决。 通常(尽管并非总是)需要优化内存占用。 但很少需要优化流量控制和流量控制。 表达逻辑。 在那些确实有必要的情况下,这很少是足够的。
诺曼的回答非常好。 不知何故,你经常会做一些“过早的优化”,这实际上是最佳实践,因为众所周知,不这样做是完全低效的。
例如,要添加到 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++) 的速度更快,而且仍然可读,所以没问题。
等等。 但这种微观优化决不应该以牺牲代码的可读性为代价。
值得注意的是,Knuth 的原始引用来自他撰写的一篇论文,该论文提倡在精心选择和测量的区域中使用 goto
作为消除热点的一种方法。 他的引用是他添加的一个警告,以证明他使用 goto 来加速这些关键循环的理由。
[...] 再次强调,这显着降低了整体运行速度,
比如说,如果 n 的平均值约为 20,并且如果搜索例程
在程序中执行大约一百万次左右。 这样的循环
优化[使用gotos
] 并不难学,而且正如我所见
据说,它们只适用于程序的一小部分,但它们
通常可以节省大量成本。 [...]
并继续:
当今许多软件工程师所共有的传统智慧
呼吁忽视小规模的效率; 但我相信这是
只是对他们所看到的滥用行为的过度反应
精明大愚的程序员,他们无法调试或维护
他们的“优化”程序。 在已建立的工程学科中
12% 的改进很容易获得,但绝不被认为是微不足道的; 和我
相信同样的观点应该在软件工程中盛行。 的
当然我不会费心在一次性工作中进行这样的优化,
但当涉及到准备高质量节目时,我不想
将自己限制在那些无法提高效率的工具上 [即goto
在这方面的声明]。
请记住他如何在引号中使用“优化”(该软件实际上可能并不高效)。 另请注意,他不仅批评这些“小事聪明,大事愚蠢”的程序员,而且还批评那些建议您应该始终忽略效率低下的人。 最后,说一下经常被引用的部分:
毫无疑问,追求效率会导致滥用。
程序员浪费大量时间思考或担心
关于,他们程序的非关键部分的速度,以及这些
尝试提高效率实际上会产生强烈的负面影响
考虑了调试和维护。 我们应该忘记小事
效率,比如说 97% 的时间; 过早优化是根源
一切邪恶。
...然后更多地了解分析工具的重要性:
对事物的哪些部分做出先验判断通常是错误的
计划确实至关重要,因为普遍的经验
一直在使用测量工具的程序员一直认为他们的
直觉的猜测失败了。 使用这些工具七年之后,
我已经确信从现在开始编写的所有编译器都应该是
旨在为所有程序员提供反馈,表明什么
他们的部分项目成本最高; 确实,这个反馈
除非特别指定,否则应自动提供
已关闭。
人们到处滥用他的引言,当他的整篇论文都在提倡微优化时,人们常常暗示微优化还为时过早! 他批评的一群人响应了这种“传统智慧”,因为他总是忽视小细节的效率,他们经常滥用他的引言,这句话最初是针对那些不鼓励一切形式的微观优化的人的。 。
然而,这句话有利于由经验丰富的手持分析器的人使用时适当应用微优化。 今天的类比可能是这样的,“人们不应该盲目地优化他们的软件,但是自定义内存分配器在关键领域应用以改善引用局部性时可以产生巨大的差异,”或者,“使用 SoA 代表手写的 SIMD 代码确实很难维护,您不应该到处使用它,但是当由经验丰富且受指导的人员正确应用时,它可以更快地消耗内存。“
任何时候,当您尝试像 Knuth 上面所提倡的那样,推广仔细应用的微优化时,最好添加一个免责声明,以阻止新手过于兴奋并盲目尝试优化,例如重写整个软件以使用<代码>转到。 这部分就是他正在做的事情。 他的引言实际上是一个重要免责声明的一部分,就像有人骑摩托车跳过燃烧的火坑可能会添加一个免责声明,即业余爱好者不应该在家尝试这个,同时批评那些在没有适当知识和设备的情况下尝试并受伤的人。
他认为“过早的优化”是那些实际上不知道自己在做什么的人所应用的优化:不知道优化是否真的需要,没有使用适当的工具进行测量,也许不了解优化的本质他们的编译器或计算机体系结构,最重要的是,是“小事聪明,大事愚蠢”,这意味着他们通过试图省钱而忽视了优化(节省数百万美元)的巨大机会,并且在创建他们无法做到的代码时更有效地调试和维护。
如果您不属于“小事聪明,大事愚蠢”类别,那么您就不会根据 Knuth 的标准过早地进行优化,即使您使用 goto
来加速关键循环(这对于当今的优化器来说不太可能有多大帮助,但如果它确实如此,并且在真正的关键区域,那么您就不会过早地优化)。 如果你真的将你正在做的事情应用到真正需要的领域,并且他们真正从中受益,那么在高德纳眼中你就做得很好。
正如我在类似问题上发布的,优化规则是:
1) 不要优化
2)(仅适用于专家)稍后优化
什么时候优化过早? 通常。
例外情况可能出现在您的设计中,或者出现在大量使用的封装良好的代码中。 过去,我曾研究过一些时间关键的代码(RSA 实现),其中查看编译器生成的汇编器并删除内部循环中的单个不必要的指令,从而获得了 30% 的加速。 但是,使用更复杂的算法所带来的加速比这要高出几个数量级。
优化时要问自己的另一个问题是“我在这里所做的相当于对 300 波特率调制解调器进行优化吗?”。 换句话说,摩尔定律很快就会使你的优化变得无关紧要。 许多扩展问题可以通过投入更多硬件来解决。
最后但并非最不重要的一点是,在程序运行速度太慢之前进行优化还为时过早。 如果您正在谈论的是 Web 应用程序,您可以在负载下运行它以查看瓶颈在哪里 - 但您很可能会遇到与大多数其他站点相同的扩展问题,并且将应用相同的解决方案。
编辑:顺便说一句,关于链接的文章,我会对所做的许多假设提出质疑。 首先,摩尔定律在 20 世纪 90 年代不再发挥作用的说法并不正确。 其次,用户的时间比程序员的时间更有价值这一点并不明显。 无论如何,大多数用户(至少可以这么说)不会疯狂地使用每个可用的 CPU 周期,他们可能正在等待网络执行某些操作。 另外,当程序员的时间从实现其他事情上转移到在用户打电话时程序执行的事情上减少几毫秒时,就会产生机会成本。 任何比这更长的时间通常都不是优化,而是错误修复。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
我的看法是,如果您在不知道在不同场景中可以获得多少性能的情况下优化某些内容,那么这是一种过早的优化。 代码的目标确实应该使人们最容易阅读。
The way I see it is, if you optimize something without knowing how much performance you can gain in different scenario IS a premature optimization. The goal of code should really making it easiest for human to read.