摩尔定律让计算机资源限制处在弹性状态,如果价格保持不变,每经历18个月内就会翻倍,那为什么应用程序的要求还需要尽可能的压缩资源使用呢?如果CPU会有现在的两倍速度,那为什么还要削减运行中的程序呢?换句话说,为什么要费心去优化程序呢?不应该让程序正常运行,当计算机资源遇到瓶颈时由摩尔定律来让我们摆脱困境吗?Randall Hyde主张即便是计算能力能翻倍,优化还是很重要的一环,但是过早地优化反而会是徒劳地浪费时间而已。
每个有几年工作经验或者受过教育的开发者会都听到一句习语“过早地优化是万恶之源”,这句话引用自Tony Hoare先生(由Donald Knuth推广),在软件工程师之间广为流传。不过,同许多赫赫有名的名言一样,这句话最初的含义被完全被错误理解,软件工程师在工作中也对这句话的理解也不同。计算机系统性能从最初MHz到100xMHz再到GHMHz,相比起其他问题来计算机的软件的性能显得尤为重要。现在,很多软件工程师将这句话理解成了“你完全不需要优化你的代码!”。有趣的是,用户们却不会这么想。Hoare的话被误解为“优化是不必要的”才是最大的遗憾。现在应用越来越臃肿,而且经常无响应,就促使软件工程师们去思考他们到底在项目中应该如何应用Hoare的话。
你可能会说,本文不是“在警告开发者不要过早地优化”。本文原本的目的是调查软件工程师是如何(错误地)运用Hoare的话而无法开发出高性能的应用程序。但愿本文能够鼓励软件开发者改变对应用性能的固有的错误观点。
“过早优化是万恶之源”这句话长期以来嘲讽软件工程师从开发之初直到软件最终版本,完全忽视了应用程序的性能(从成本和上线时间原因通常会忽略)。然而,Hoare并没有说“在应用的开发早期关注应用性能是万恶之源”。他明确地说了“过早的优化”,而优化的意思在这里是与原来事物表现的完全不同。优化常常是一系列动作,比如用汇编语言计算周期和指令。在项目设计初期代码的编写可能与你想象的有所出入,特别是代码不太稳定的时候。所以,Hoare的用意很明显。下面我复制的一段Charles Cook的短文,其中解释了Hoare的话引起的问题。
我总是在想,由于这句话被应用到与问题完全不相符的领域中,所以软件设计师经常陷入到误区。这句话完整的版本是“我们应该忘记大约占97%时间的低效率工作:过早的优化是万恶之源”。在性能遇到明显的瓶颈之前,我们通常不值得花费太多时间来过分优化代码。但是,在系统层面设计软件时,性能问题就应该在设计之初就要深刻地考虑到。优秀的软件开发者会自动地把性能优化考虑进去,培养一种能对可能出现的问题有感知直觉。一个缺少经验的开发者则不会为这种问题烦恼,错误地相信后期的一点修补会修复所有的问题。
Hoare和Knuth真正想说的是,软件工程师在担心细节优化(比如一条语句会占用多少CPU运算周期)之前,更应该担心其他的问题(比如合适的算法设计和恰当的算法实施)。
很多软件工程师误解了优秀工程师的理念,因此编写出了十分糟糕的代码。
观察 #1
“过早的优化是万恶之源”这句话已经演变成了“优化是万恶之源”,因此,优化在工作中应该摒弃。
观察 #2
许多软件工程师相信,优化是确保应用程序能有足够的性能,但是,这些工程师只有在关键时候这样做,设计软件时并不会考虑到应用程序的性能。
观察 #3
软件工程师使用帕累托效应来拖延对软件性能的思考,错误地认为性能问题会在软件开发周期结束时很容易的解决掉。这种想法忽视了事实上20%的代码会耗费80%的运算时间,并且低效的代码分布贯穿在整个源码中,并没有那么容易地简单修改就能优化。进一步说,帕累托效应对那些一开始就没有考虑周期的代码来说并不适应(例如,一些很差劲的算法和算法的事实,几处代码就能完全改变系统的效率)
观察 #4
很多软件工程师开始相信,到了应用发布的时候,CPU性能会得到极大的提高,能够弥补部分代码的缺陷。在上世纪90年代来说可能是没错的,但是现在并不不是上世纪90年代,CPU性能提高的幅度在收紧。
观察 #5
软件工程师已经开始相信他们无法预测应用程序运算的开销。因此,他们不会优化部分显而易见的问题代码的来提高性能,因为他们没有证据来相信修改这部分问题代码不会影响整个系统的性能。
观察 #6
软件工程师开始相信他们的个人时间比起CPU的时间来更值钱,因此浪费CPU的周期来缩短开发周期是的合理的。然而,他们可能已经忘记,用户的时间比他们的时间更加值钱。
观察 #7
优化是一个极其困难和成本很高的环节,很多工程师抱怨这个环节会推迟应用程序上架时间,进而压缩了利润。这在某种程度上或许是正确的,但是忽视了一系列低性能产品的所压缩的利润(特别是他们在应用市场存在竞争时)。
观察 #8
引用一条软件工程师最基本的规则,当考虑到性能问题时,选择合适的算法比起优化来说更有效。这句话忽略了一个事实:更优的算法可能想到或者很难编写。无论怎样,这不是一个不去优化代码的借口。
观察 #9
没必要在一开始就设计出合适的算法,后期会有更合适的算法来替代。因此,由于在后概率会替换修改算法,在项目起步时可以忽视软件性能。可悲的是,采用这种想法的人往往会写出后期也无法优化的代码。
那么我们应该如何转变这种趋势,并且说服软件工程师他们要编写出更有效率的应用而不是仅仅是嘴上说说。必要的是改变观念,开发者必须确信对于开发软件来说效率是极为重要的标准。
首先以Hoare的原话开始。他并没有说,“优化是项目毁掉的根源”,所以软件工程师必须做到必要的优化。优化是标准软件开发中的一个环节,同样还有测试、调试以及质量保证。的确,优化会比较困难,而且要求有相当程度的经验才能做到合理的优化。然而,回避并不是解决困难问题的方法,获得经验的途径只有通过不断的优化代码的练习才能获得。测试也同样很困难而且会消耗大量的时间,但是我们都没有见到过专业的软件工程师会承认因为困难和费时而不去做必要的测试。
其次,Hoare并没有只说了“过早优化是万恶之源”。他说“我们应该忘记大约占97%的时间的低效率工作:过早的优化是万恶之源”。在系统设计和开发初期,低效率(通过微优化得来的东西)是完全不赞同的。然而重点在Hoare并没有说“永远地忘掉低效率”。相反的,他说“大约占97%的时间”。这就意味着有3%的时间我们真的需要担心低效率。这听起来不算多,但意味着每33行代码中就有1行存在低效率问题。有多少开发者能经常考虑到低效率问题呢?过早的优化从来都是不可取的,但事实上在开发过程中担心低效率问题永远不会太早。如何来确定开发过程中哪些优化值得考虑,而哪些优化还为时过早呢?Charles Cook说过一句很恰当的话“优秀的软件开发者会自动地把性能优化考虑进去,培养一种能对可能出现的问题有感知直觉。一个缺少经验的开发者则不会为这种问题烦恼,错误地相信后期的一点修补会修复所有的问题”。那么缺少经验的开发者要如何学习在恰当时候做优化呢?第一步是要剖析项目,通过对各个部分程序的计时就可以简单预测代码的性能。第二步是缺少经验的开发者要有做错事情的准备,如果你对一部分代码做了项目本身不需要的优化,其实你就已经对项目造成了损失。除了造成的额外的维护之外,还有需要花费大量时间来写一些不必要的代码,别忘了这里面你会得到许多珍贵的经验,在以后的项目中你就能避免出现相同的错误。
再次,在工作中,软件工程师需要在项目初期就要对应用程序性能做合理的设计。“过早优化是万恶之源”概念与软件设计准则和编码工作相抵触。缺少对应用性能的思考,即便是在开发的最后阶段再展开大量的优化工作,系统的性能也很难再有效提高,通常,唯一能提高系统性能的方法就是重写项目。太多的工程师用Hoare的话当做借口来避免设计和开发高性能代码带来的繁杂工作。然而,Hoare并没有提到过“效率是万恶之源”。设计和优化阶段是完全分开的,Hoare的话只能应用在优化阶段,并不适用于设计阶段。如果过早的优化是所有问题的源头,那么在设计和开发阶段缺乏对性能优化的规划,才是万恶之源。过早地优化可能会导致代码的难以修改、维护和阅读,另一方面来说,不合适的设计和开发则会导致项目全部重写。
开发者不仅要熟练使用开发工具,而且要懂得代码的运行。我最喜欢的一句话,是我从Rico Mariani听来的:“一定不要无意地忽视性能”。太多的软件工程师在编写代码的时候违反这句话的理念,例如,许多的开发者对他们使用语言的高级语法的运行开销一无所知,正如他们在大学里学习的《数据结构》和《算法分析》课程中所讲的那样,大多数开发者认为程序中的每一条语句都同样只需要一个单位时间的运算。事实上,不同的语句所需的运算时间是不同的,有时候一种类型的语言会比另一种运算速度快很多,仅根据程序性能来选择程序中的控制语句不是一个明智做法,但是从几个相同作用的语句中选择一条最高效的写入程序才是最合适的做法。不幸的是,很少有开发者能真正的懂得开发细节,所以他们很难编写出最合适的代码。这是忽视性能最经典的案例。
历来软件工程师都会用“过早优化是万恶之源”来作为自己缺乏对开发细节认识的借口,例如无法从两条(或多条)等效指令中最高效率的一条。他们主张“代码在频繁修改时,优化会耗费太多的时间”。然而,有经验的开发者不会花太多额外的时间来选择最优的指令,通过以往的经验,他们能不假思索就得出并且应用最合适的指令。自动的选择出合适的(如果不是最优的)指令,应该是每一位软件工程师都要努力达到的目标。通常情况下,软件工程师会选择脑海中想到的第一条指令或者是在当时看来最合适的指令。但如果工程师不去考虑他们编写的代码质量,那就会产生不好的结果。
保证高性能高质量的应用程序并不像很多软件工程师所想的那么困难。开发高性能软件并不需要太高的成本,高性能的软件也不会推迟太长软件的发布时间。为了达到这个目标,系统设计者就需要在初期考虑到产品的性能问题,开发者需要在编写代码的时候仔细地考虑他们的开发细节,团队需要在开发过程中确保软件质量,以确保最终交付的软件质量在预计范围之内。如同其他任何漏洞一样,在开发早期调整性能会使整体成本下降许多,这就是为什么优化阶段之前通常不考虑效率问题的原因。
软件工程师学习如何才能写得出更高效代码呢?首先,第一个课题就是掌握计算机的组成。因为所有的软件都会在计算机上运算,如果你想编写出计算机运行最有效的代码,就需要了解代码是如何在计算机中运行的。计算机组成涵盖了许多科目,如数据描述,内存存取和组成,缓存运行机制,多种数据结构的使用,计算机如何执行算数和逻辑运算,以及计算机如何执行命令。这些是影响到软件系统开发的所有基础知识。
第二个课题是学习汇编语言。尽管很少开发者实际中会使用汇编语言来开发应用程序,但如果你想在高级语言和底层CPU之间建立通信,汇编语言是非常关键的。我不建议所有的开发者都成为汇编语言专家,写出更优秀的汇编代码,我的意思是对机器底层代码有深刻理解的高级语言开发者会编写出十分高效的代码。
注意,学习理解高级语言中语句的开销,与学习哪一种汇编语言和哪一种CPU指令集都无关紧要。你真正要学习的是计算机基础运算的开销。从高级语言角度来看,无论是PowerPC还是Intel x86,所需开销相差不大。但在更精细粒度的时间来看,就是Hoare所讲到的内容。
开发者学习汇编语言的主要原因是能够理解在日常编程工作中不同语句的开销。学习汇编语言是非常重要的一步,但是这足以成为一个合格的软件工程师。第三个重要的课题是学习基本编译器的构造,学习高级语言语句是如何转变为机器代码的。
“一定不要无意地忽视性能”,Rico Mariani说的这句话很有警示作用,注意他并没有说“一定不要忽视性能”,他说开发者不应该无意地忽视了性能。人们对程序的语言开销不太清楚时,就会无意间下意识地忽略这段代码性能。通过学习系统如何翻译或解释高级语言语句,你就会了解不同语句的开销,并且不会使用一些低效率的语言结构。你会对比已有的代码块与其他效果相同的代码块,通过多种条件(包括可读性、可维护性和性能)筛选得出最恰当的,虽然可能不是最有效的。然而,如果你放弃了性能(如提高可读性和可维护性),你的损失是必然的。
Tony Hoare说的“我们应该忘记大约占97%的时间的低效率工作:过早的优化是万恶之源”已经根本上影响了工程师开发应用程序的方式。一种对这句话错误的解释,特别是“过早的优化是万恶之源”,让许多工程师(还有相关专业在校生)相信优化是不重要的。转变这种应用程序性能低下的现状,软件工程师必须要摒弃性能并不重要的态度。只要他们发现编写高性能软件是有意义的,下一步他们就会学习高级语言编译器和解释器是如何处理这些语言结构的,一旦工程师理解了这个过程,选择恰当的高级语言结构就会形成一种习惯,而且这种习惯不会产生额外的开发成本。Hoare的原本的话,在微观层面上,过早的优化并不是一个好主意,但这不并不是主张工程师不去考虑到应用程序性能,过早优化谬误,就如同“过分关注性能”,不应引导软件开发的方向。