Knuth 引用中最关键的一点是“小家子气,大失误”。这就是他最终描述的过早进行优化的人——当有很多钱可节省时,却只为省下几分钱而争论不休,并且努力维护他们的“优化”(请注意他在此处使用引号的方式)软件。我发现很多人经常只引用 Knuth 论文的一小部分。值得注意的是,他的论文主张使用 goto 来加速软件中的关键执行路径。更完整的引用如下:
[...] 如果n的平均值约为20,并且在程序中执行搜索例程大约一百万次左右,那么这将明显节省总运行速度。使用goto进行此类循环优化并不难学习,正如我所说,它们只适用于程序的一小部分,但它们通常会产生可观的节省。 [...] 许多现今软件工程师共享的传统智慧是忽略小规模效率问题;但我认为这只是对那些吝啬而愚蠢的程序员滥用优化的过度反应,他们无法调试或维护他们的“优化”程序。在成熟的工程学科中,轻松获得12%的改善从来不被视为边缘;我认为在软件工程中应该有同样的观点。当然,如果只需要完成一项任务,我不会费心进行这样的优化,但是当涉及到准备高质量的程序时,我不想限制自己只使用拒绝这种效率的工具。毫无疑问,追求效率的目标会导致滥用。程序员浪费了大量时间思考或担心其程序的非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时具有强烈的负面影响。我们应该忘记小效率,例如97%的时间;过早地优化是万恶之源。通常对程序的哪些部分真正关键做出先验判断是错误的,因为使用测量工具的程序员的普遍经验是他们的直觉猜测失败了。在使用这些工具七年后,我已经确信,所有从现在开始编写的编译器都应该被设计为向所有程序员提供反馈,指示他们程序的哪些部分成本最高;除非明确关闭,否则应自动提供此反馈。程序员知道他的例程的哪些部分真正重要
之后,像双倍循环这样的转换将是值得的。请注意,此转换引入了
go to
语句--其他几个循环优化也是如此。
这段话来自一个曾经非常关注微观层面性能的人,当时(优化器现在已经进步了很多),他利用了
goto
来提高速度。
而 Knuth 所提出的“过早优化”的核心是:
- 基于直觉/迷信/人类直觉进行优化,没有过去的经验或测量数据(盲目优化而不知道自己在做什么)。
- 以省几分钱为代价的优化(无效的优化)。
- 寻求所有东西的绝对最高效率。
- 在非关键路径中寻求效率。
- 在你几乎无法维护/调试代码时尝试优化。
这些都与您优化的时间无关,而与经验和理解有关--从理解关键路径到理解实际提供性能的内容。
像测试驱动开发和主要关注接口设计等事项并没有在 Knuth 的论文中涵盖。这些是更现代的概念和想法。他主要关注实现。
尽管如此,这是对Knuth建议的一个很好的更新——通过测试和接口设计首先确立正确性,并留下优化空间而不破坏一切。
如果我们试图应用现代解释,我会在其中加入“交付”。即使您正在通过测量收益来优化软件的真正关键路径,如果它从未交付,那么世界上最快的软件也毫无价值。记住这一点应该有助于您做出更明智的妥协。
“我倾向于只是多次访问数据库,我认为这是正确的做法。完成项目更重要,我觉得我因为这样的优化而陷入困境。我的问题是:在避免过早优化时,这是正确的策略吗?”
根据上述一些要点,你需要自己发展最佳判断力,因为你最了解自己的需求。
我建议一个至关重要的因素是,如果这是处理重负载的性能关键路径,请以一种方式设计公共接口,以便留出足够的优化空间。
例如,不要为具有客户端依赖性的粒子系统设计
Particle
接口。这样做留下了优化的空间很小,因为你只能使用封装状态和单个粒子的实现。在这种情况下,您可能需要对代码库进行级联更改以进行优化。如果赛车道只有10米长,那么赛车无法利用其速度。相反,应该设计面向
ParticleSystem
接口,该接口聚合了一百万个粒子,例如通过高级操作尽可能批量处理粒子。这样做可以为您留下足够的空间进行优化,而不会破坏设计,如果您发现需要优化,可以随时进行优化。
我完美主义者希望第一次通过就使所有东西都达到最佳状态和完美状态,但我发现这会使设计变得非常复杂。
现在这部分听起来有点过早。通常,您的第一次尝试应该是简单的。即使您正在执行一些冗余工作,简单性通常与相当快的速度相较手牵手。
无论如何,我希望这些观点能够帮助您考虑更多事情。