对我来说,Knuth 引用的关键方面是“一分钱一分货”。这就是他最终描述过早优化器的方式——当有英镑要节省时,有人为了节省便士而讨价还价,并努力维护他们的“优化”(注意他在这里如何使用引号)软件。
我发现很多人经常只引用 Knuth 论文的一小部分。值得注意的是,他的论文主张使用goto
它来加速软件中的关键执行路径。
更完整的报价:
[...] 如果 n 的平均值大约为 20,并且如果在程序中执行大约一百万次搜索例程,这将显着降低整体运行速度。这种循环优化[使用 goto] 并不难学,而且正如我所说,它们只适用于程序的一小部分,但它们通常会产生大量节省。[...]
当今许多软件工程师所共有的传统智慧要求忽略小规模的效率。但我相信这只是对他们看到的一分钱一分货的愚蠢程序员所实施的滥用行为的过度反应,他们无法调试或维护他们的“优化”程序。在已建立的工程学科中,12% 的改进很容易获得,但绝不会被认为是微不足道的;我相信同样的观点应该在软件工程中占上风。当然,我不会费心在一次性工作上进行这样的优化,但是当涉及到准备高质量程序的问题时,我不想将自己限制在无法提高效率的工具上。
毫无疑问,效率的圣杯会导致滥用。程序员浪费大量时间来思考或担心程序中非关键部分的速度,而在考虑调试和维护时,这些提高效率的尝试实际上会产生强烈的负面影响。我们应该忘记小的效率,比如说 97% 的时间;过早的优化是万恶之源。
对程序的哪些部分真正关键做出先验判断通常是错误的,因为一直使用测量工具的程序员的普遍经验是,他们的直觉猜测失败了。在使用这些工具七年之后,我确信从现在开始编写的所有编译器都应该设计为向所有程序员提供反馈,表明他们程序的哪些部分成本最高;事实上,这个反馈应该是自动提供的,除非它被特别关闭。
在程序员知道他的例程的哪些部分真正重要之后,像加倍循环这样的转换将是值得的。请注意,这种转换引入了go to
语句——其他几个循环优化也是如此。
所以这是来自一个实际上非常关注微观层面的性能的人,当时(优化器现在已经变得更好了),正在goto
利用速度。
Knuth 建立“过早优化器”的核心是:
- 基于预感/迷信/人类直觉进行优化,没有过去的经验或衡量标准(盲目优化,实际上不知道自己在做什么)。
- 以节省几分钱而不是英镑的方式进行优化(无效的优化)。
- 为一切寻求绝对的终极效率峰值。
- 在非关键路径中寻求效率。
- 当您几乎无法维护/调试代码时尝试优化。
这些都与您的优化时间无关,而是经验和理解——从理解关键路径到理解实际提供性能的内容。
Knuth 的论文中没有涉及诸如测试驱动开发和对界面设计的主要关注之类的事情。这些是更现代的概念和想法。他主要专注于实施。
尽管如此,这是对 Knuth 建议的一个很好的更新——首先通过测试来寻求建立正确性,并且界面设计可以让您在不破坏一切的情况下进行优化。
如果我们尝试对 Knuth 进行现代解释,我会在其中添加“ship”。即使您正在通过衡量的收益优化软件的真正关键路径,如果世界上最快的软件永远不会发布,它也是毫无价值的。牢记这一点应该可以帮助您做出更明智的妥协。
我倾向于多次访问数据库,我认为这是正确的做法。更重要的是我完成了这个项目,我觉得我因为这样的优化而被挂断了。我的问题是:这是避免过早优化时使用的正确策略吗?
当您最了解自己的要求时,考虑到上述一些要点,这将取决于您做出最佳判断。
我建议的一个关键因素是,如果这是一条处理繁重负载的性能关键路径,那么以一种留有足够优化空间的方式设计您的公共接口。
例如,不要设计一个具有客户端依赖于Particle
接口的粒子系统。当您只有封装状态和单个粒子的实现可以使用时,这就没有优化的空间了。在这种情况下,您可能必须对代码库进行级联更改才能进行优化。如果道路只有 10 米长,赛车就无法利用它的速度。而是针对ParticleSystem
聚合一百万个粒子的界面进行设计,例如,在可能的情况下使用更高级别的操作来处理散装粒子。如果您发现需要优化,这将为您留出足够的优化空间而不会破坏您的设计。
我完美主义的一面希望在第一次通过时就使一切都变得最佳和完美,但我发现这使设计变得相当复杂。
现在这部分听起来有点为时过早。一般来说,你的第一遍应该是简单的。简单性通常与相当快、比您想象的要快,即使您正在做一些多余的工作也是如此。
无论如何,我希望这些观点至少有助于增加一些考虑因素。