6.1 源码级的优化

GCC使用的第一个优化级别是源码级别的,不需要任何机器指令相关的知识。有很多源码级的优化技术--本节介绍两个常用技术:函数内联化和消除常用子表达式。

6.1.1 消除常用子表达式

一个源码级的优化方法当在涉及计算一个表达式时非常容易理解,通过减少已经计算了的结果。例如,下面的表达式:

x = cos(v)(1+sin(u/2)) + sin(w)(1-sin(u/2))

可以使用一个临时变量t重写这个表达式来减少不必要的额外计算项目sin(u/2):

t = sin(u/2)

x = cos(v)(1+t) + sin(w)(1-t)

这种重写叫做减少常用子表达式(CSE),当使用了优化选项时这种优化是自动完成的。CSE是非常强大的因为它不仅提高了速度而且减少了代码体积。

6.1.2 函数内联

另一种类型的源码级优化叫做函数内联,作用是增加函数调用效率。

无论何时函数的使用都会消耗额外的CPU时间来执行调用:它必须将函数参数存储到合适的寄存器和内存区域,跳转到函数开头(如果有必要会加入新的合适的虚拟内存页或者CPU缓存)开始执行代码然后当函数执行完毕后返回到原先运行的地方。这些额外的工作被称为函数调用消耗。函数内联会把函数调用替换为函数代码本身来减少这种消耗。

在大多数情况下函数调用消耗对于整个程序运行的时间来说是微不足道的。只有当一个函数包含少量指令并且这些函数占用了大量的运行时间时函数调用消耗会占用很大一部分运行时间。

如果只有一个函数调用点时内联总是适当的选择。如果函数调用比此函数体需要更多指令时内联也是更好的选择。C++中简单的辅助函数经常出现这种情况,内联对于这种情况有很大的益处。另外内联可能促进更多优化,例如通过合并几个单独的函数为一个大型函数来减少常用子表达式。

下面的函数sq(x)是一个典型的可以使用内联的简单函数。它通过参数x计算x^2的值:

double

sq (double x)

{

return x * x;

}

这个函数非常小所以函数调用所花费的时间和函数本身执行的时间差不多。如果这个函数在一个循环中被调用,就像下面的代码则函数调用消耗会非常巨大:

for (i = 0; i < 1000000; i++)

{

sum += sq (i + 0.5);

}

内联优化使用函数体本身代替函数调用,得到下面代码:

for (i = 0; i < 1000000; i++)

{

double t = (i + 0.5); / temporary variable /

sum += t * t;

}

减少函数调用并在函数内部执行乘法使循环执行得到最大效率。

GCC是根据一个数字选择内联函数,例如函数体积足够的小。作为一个优化选择,内联只在每个目标文件中进行。Inline关键字可以用于明确要求指定函数无论什么情况都应该被内联,包括在其他文件中使用。GCC参考手册”Using GCC”提供了inline关键字的所有细节,和static和extern关键字一起使用来控制链接指定内联函数。