购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.4 以底层语言思考

Java在20世纪90年代末期逐渐流行开来后,我们常常听到如下的抱怨:

Java的解释性代码逼着我写软件时要注意许多东西:我不能用C/C++中的线性查找,而只能用二分查找之类的算法—这种算法虽好,但实现起来更麻烦。

类似这种牢骚真实反映了使用优化型编译器时存在的主要问题—它们让程序员变得懒惰。尽管优化型编译器在过去几十年突飞猛进,但谁也不可能摆平那些写得糟糕的高级语言源代码。

许多高级语言程序员很天真,想当然地认为现代编译器的优化算法非常厉害,不管给编译器塞入什么东西,都能得到高效的代码。然而事实并不是那么回事:尽管编译器的确能做一大堆工作,可将写得好的高级语言代码转换为高效的机器码;但如果送入的是糟糕的源代码,让优化算法乱套照样容易得很。事实上,不止一个C/C++程序员吹嘘其编译器多么能干。他们从没意识到,由于其程序实在不敢恭维,编译器的活儿干得有多差。问题在于,他们从未看一眼编译器根据其程序源码所生成的机器码。他们盲目地以为编译器会干得很好,因为听说编译器产生的代码几乎和汇编语言高手写的代码一样出色。

1.4.1 编译器生成的机器码只会与送入的源代码质量相配

编译器不会为了改善软件的性能而去修改算法,这是毋庸置疑的。例如,如果使用线性查找算法而非二分查找算法,别指望编译器替你换成更好的算法。当然,优化器也许能加快线性查找算法的速度,比如使代码速度提高至原来的两到三倍,但这种改进与采用更好的算法相比不值一提。事实很容易证明,对于足够大的数据库,解释器的二分查找算法即便未经优化,仍比顶尖的编译器采用的线性查找算法快得多。

1.4.2 如何协助编译器生成更好的机器码

假设我们为应用程序选定了尽可能好的算法,并且不惜血本购置了最好的编译器。要想编写出有效率的高级语言代码,还有什么要做的吗?是的!我们还得做一些事情。

编译器业界的一个秘密就是大多数编译器的评测分数都有作弊之嫌。现存编译器的评测分数多数都基于某种指定要用的算法,但算法具体怎样实现则由编译器厂商在其特定语言中决定。既然编译器厂商通常知道其编译器在送入特定代码序列时会表现得如何,那么为了产生尽可能好的可执行代码,他们能够写出相应的高级语言代码序列。

有人会觉得这是欺骗行为,其实还谈不上欺骗。如果编译器在一般条件下—即没有为了提高评测分数而采用专门的代码生成技巧—能生成同样的代码序列,炫耀编译器的性能就无可厚非。既然编译器厂商能采取一些小窍门,你也可以这样做。在高级语言代码中,通过仔细选择所用的语句,我们可以“手工优化”编译器产生的机器码。

手工优化有若干级别。在最抽象的层,可以为软件选择更好的算法来优化程序,其技术与特定的编译器和语言无关。

抽象级别再低一层,就要基于所用的高级语言来优化代码,优化不依赖于语言的特定实现。这样的优化或许无法用到其他语言,但适用于该语言的不同编译器。

再下一级,可以考虑代码的组织,优化只对某个厂商的编译器,或者只对某编译器的特定版本有用。

最低一级,就得考虑编译器发出的机器码,从而调整我们在高级语言中的语句写法,促使编译器生成希望的机器指令序列。Linux内核就是这种办法的例子。据说内核开发者是为了控制GCC编译器所产生的80x86机器码质量,才不断调整其C语言代码的。

尽管这种开发过程近乎夸张,但有一点确凿无疑:经历这个过程后,程序员将能生成足够好的机器码。这种代码可以与汇编语言程序员生成的代码平分秋色;程序员在为编译器产生的代码与手工汇编的性能比较争论时,这种编译器的输出才是他们该吹牛的地方。事实是,大部分人都不会这么极端,其高级语言代码从来都没有达到这种境界。然而还有一个事实是,倘若精心用高级语言编写,程序可以基本达到严谨汇编代码的效率。

那么,编译器生成的代码能和汇编语言高手写的代码旗鼓相当吗?正确答案是“否”。毕竟汇编语言高手总能看到编译器输出,并据此不断完善自己的代码。不过,认真的程序员以高级语言(比如C/C++语言)所写的代码—如果其代码便于被编译器转换为高效机器代码—效率可以逼近高效机器代码。所以,真正的问题是“我该如何编写高级语言代码,使编译器转换为尽量高效率的代码?”嗯,回答这个问题正是本书的目标。要是用一句话回答,就是“以底层(汇编)语言思考,用高级语言编程”。我们来看看怎样做到这一点。

1.4.3 在用高级语言编程时如何以汇编语言思考

高级语言编译器将语言中的语句转换为一条或多条机器语言(即汇编语言)指令的序列。应用程序占用的地址空间和花在执行上的时间,与机器指令数、编译器发出的机器指令类型息息相关。

然而在高级语言中用不同方法取得同样结果,并不是意味着编译器对每种方法产生的指令是一样的。典型的示例就是if语句和switch/case语句。多数入门性质的编程文章指出if-elseif-else语句等效于switch/case语句。我们来考虑下面的简单C程序:

尽管这两段代码在句法上等效,即它们能计算出同样的结果,然而不能保证编译器会为这两个例子生成同样的机器指令。

哪个更好呢?如果我们不清楚编译器如何把这些语句转换成机器码,对各种机器指令的效率差异又不甚了解,是无法选择其中更优的那个代码序列的。程序员要是对编译器转换这两个序列的机理成竹在胸,就会根据其期望编译器生成什么质量的代码,而做出明智的选择。

通过以底层语言思考,而用高级语言编程,程序员就能协助优化型编译器来达到手工优化的汇编语言代码的质量。不过令人沮丧的是,其负面情形通常也成立:假如程序员不考虑其高级语言代码的底层结果,编译器几乎不可能生成足够好的机器码。 TZGi4mrca6VMYxf3drcZs5EybJO3fzFSUjBmj4UDbWbfUk/zVnLwfinoXGwwYv9F

点击中间区域
呼出菜单
上一章
目录
下一章
×