优化C++代码(3)常量合并

开发 后端
这篇文章讲的是常量合并,这是VC++编译器最简单的优化之一。 这种优化,是指编译器在编译时(编译期间)直接计算出表达式的结果,在生成的代码中直接用计算结果替换表达式。 这样就避免了程序在运行时执行这些计算花费的成本。

这篇文章讲的是常量合并,这是VC++编译器最简单的优化之一。  这种优化,是指编译器在编译时(编译期间)直接计算出表达式的结果,在生成的代码中直接用计算结果替换表达式。 这样就避免了程序在运行时执行这些计算花费的成本。

下面是一个例子 APP.cpp文件中的 main 函数:

  1. int main() { return 7 + 8; } 

首先,关于这篇文章的一些须知:

  1. 我们将从命令行来构建程序(而不是Visual Studio)
  2. 我们会使用Visual Studio 2012。 特别注意的是,这个版本的编译器会产生x64位代码(而不是已经过时的x86架构)在64位机子上编译。

如果你想要继续,请看下说明。实际上,你只需要从Visual Studio 列表里选择一个正确的变体。

(注意:如果你正在使用Visual Studio Express上的免费编译器,它仅仅只能运行在x86上,但是也会顺利生成x64的代码。对这个实验同样有用。)

我们可以通过命令 CL /FA App.cpp来构建示例程序。用/FA开关创建一个输出文件,用来保存编译器生成的汇编代码,可以输入type App.asm来显示:

  1. PUBLIC  main 
  2. _TEXT   SEGMENT 
  3. main    PROC 
  4.         mov     eax, 15 
  5.         ret     0 
  6. main    ENDP 
  7. _TEXT   ENDS 
  8. END 

有趣的是这条指令 move ax,15—-仅仅将15赋值给寄存器EAX(根据x64调用标准的定义,x64函数将会设置一个int值,作为函数的结果,并返回给调用者)。编译器运行期间并没有发出 7加8的指令。就像下面这样:

  1. PUBLIC  main 
  2. _TEXT   SEGMENT 
  3. main    PROC 
  4.         mov     eax, 7 
  5.         add     eax, 8 
  6.         ret     0 
  7. main    ENDP 
  8. _TEXT   ENDS 
  9. END 

(注意看了,这两段代码的***一条指令,ret 0,是指将控制权返回给调用者,并从栈里弹出0个字节。不要被误导认为是返回数值0给调用者!)

我猜到,你可能在想:这很好啊,但是哪个白痴会想到在代码里写 7+8 这样的运算?的确,你是对的,但是编译器会把这样的结构看成是有副作用的宏。看了下面的例子,你就会明白常量合并是一个很有用的优化方法:

  1. #define SECS_PER_MINUTE  60 
  2. #define MINUTES_PER_HOUR 60 
  3. #define HOURS_PER_DAY    24 
  4.   
  5. enum Event { Started, Stopped, LostData, ParityError }; 
  6.   
  7. struct { 
  8.     int        clock_time; 
  9.     enum Event ev; 
  10.     char*      reason; 
  11. }   Record; 
  12.   
  13. int main() { 
  14.     const int table_size = SECS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * sizeof Record; 
  15.     // rest of program 

我们要创建一个足够大的表保存每一秒的记录,所以table_size就是表的大小,用字节表示。很容易查看变量table_size的汇编指令:

  1. mov     DWORD PTR table_size$[rsp], 1382400     ; 00151800H 

这儿没有乘法指令,60*60*24*16=1382400 是在编译时计算的。

事实上,我们窥探下编译器的内部,会发现这种常量合并的运算非常简单,它是由前端来执行的。它并不需要后端优化器笨重的提升能力。所以它总是存在的。不管你是开启优化(使用 /O2)或者关闭优化(/Od)都没什么区别—–该优化总是自动执行的。

不管表达式有多复杂,我们都能在编译期间进行常量合并吗?—事实上,前端可以处理任意的常量算术表达式(甚至包括上面提到的sizeof,只要它们在编译时能被计算出来)和运算符(+ - * / % << >> ++ 和 –)。你甚至可以使用布尔值,逻辑运算符 和条件运算符if AND ?:。

有没有常量合并需要后端优化器的时候呢?当然有,看下面的例子:

  1. int bump(int n) { return n + 1; }  
  2.    
  3. int main() { return 3 + bump(6); }  

输入命令cl /FA /Od App.cpp,会得到信息:不能优化,谢谢!,输入 App.asm,我们会得到:

  1. mov     ecx, 6 
  2. call    ?bump@@YAHH@Z                           ; bump 
  3. add     eax, 3 

正如我们所预料的: ECX会保存***个参数6,根据x64调用约定,然后调用bump函数,结果返回给EAX,然后EAX再加3。

我们来看看如果我们使用 cl /FA /O2 App.cpp 来进行优化,会发生什么。

  1. mov eax,10 

后端优化器已经识别到bump函数很小,可以包含到调用者里(我们在后面的章节将会讲到这种优化方法,叫做内联函数)。它在编译时就能够估算出整个表达式的值,***只剩下一条单指令。很神奇,对吧?

原文链接:http://blogs.msdn.com/b/vcblog/archive/2013/07/04/optimizing-c-code-constant-folding.aspx

译文链接:http://blog.jobbole.com/47191/

责任编辑:陈四芳 来源: 伯乐在线
相关推荐

2013-09-05 09:50:11

C++代码优化

2024-01-25 16:19:27

2010-01-13 13:27:00

C++优化

2013-09-03 09:35:10

2020-07-19 10:23:13

C++进制常量

2024-01-25 11:42:00

C++编程指针常量

2010-02-05 17:49:24

C++常量引用

2023-11-15 17:58:58

C++代码

2011-07-12 11:15:46

C++

2023-10-30 10:29:50

C++最小二乘法

2021-06-10 09:40:12

C++性能优化Linux

2013-09-03 09:30:42

C++代码优化

2010-01-18 16:17:53

C++代码

2010-01-14 14:40:21

C++代码

2011-05-18 18:05:47

C#C++

2011-05-18 17:56:38

C#C++

2010-01-21 10:23:53

C++代码

2010-01-14 16:35:31

C++优化

2010-01-22 13:45:36

C++代码

2010-01-18 13:42:51

C++代码
点赞
收藏

51CTO技术栈公众号