优化C++代码:常数折叠

如果你已经到了这个博客系列的中间,你可能想从最开始 开始 .

null

这篇文章探讨了常量折叠——VC++编译器执行的最简单的优化之一。  在这种优化中,编译器在编译时(在“编译时”)计算出表达式的结果,并将答案直接插入生成的代码中。  这避免了在程序运行时(在“运行时”)执行这些相同计算的成本。

下面是一个例子。  小小的 主要的 函数,存储在文件中 应用程序.cpp :

int main(){返回7+8;}

首先,一些 管理员 关于此博客:

  • 我们将从命令行(而不是visualstudio内部)构建程序
  • 我们将使用VisualStudio2012。  尤其是生成x64代码(而不是老化x86体系结构的代码)并在x64计算机上编译(即“x64 on x64”)的编译器版本

如果你想跟着走,请按照说明走 在这里找到的 :  基本上,您只需要从一个可能的“visualstudio工具”列表中选择正确的变体。

(请注意,如果您使用的是visualstudioexpress中的免费编译器,那么它只在x86上运行,但很乐意为x64生成代码:“x86上的x64”。  这同样适用于实验)

我们可以使用以下命令构建示例程序: CL/FA应用程序cpp .  这个 /FA公司 switch创建一个输出文件,其中包含编译器为我们生成的汇编代码。  您可以通过以下方式显示: 键入App.asm 显示:

公开  主要的 _文本   段 主要的    过程 压敏电阻     eax,15岁 雷特     0 主要的    ENDP公司 _文本   末端 结束

有趣的是 移动eax,15 指令–它只是将值15插入EAX寄存器(根据x64调用标准的定义,这是x64函数设置 内景 它将作为函数的结果返回给调用方的值)。  编译器没有 发出在运行时将添加7到8的指令。  他们可能会这样:

公开  主要的 _文本   段 主要的    过程 压敏电阻     eax,7个 添加     eax,8个 雷特     0 主要的    ENDP公司 _文本   末端 结束

(请仔细注意,最后一条指令,在两个片段中, 返回0 .  这意味着:将控制权返回给调用者,并从堆栈中弹出零字节。  不要误以为这意味着将值0返回给调用者!)

让我猜猜:你可能在想“这一切都很好,但真的!  哪种白痴会写包含算术的代码 7 + 8 ”?  当然,你是对的。  但是编译器 看到这样的构造,通常作为宏的副作用。  下面是一个例子,让您相信不断折叠是一种值得优化的方法:

#定义秒/分钟  60 #定义每小时分钟60 #定义每天的小时数    24 枚举事件{Started,Stopped,LostData,ParityError};

结构{ 内景        时钟时间; 枚举事件ev; 字符*      原因; }   记录;

int main(){ const int tableu size=SECSu PERu MINUTE*MINUTESu PERu HOUR*HOURSu PERu DAY*记录大小; //程序的其余部分 }

在这里,我们将创建一个足够大的表来容纳 记录 一整天的每一秒。  所以呢 表格大小 将是该表的大小(以字节为单位)。  检查设置变量的汇编指令很容易 表格大小 :

压敏电阻 DWORD PTR表格大小$[rsp],1382400 ; 00151800H公司

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

事实上,如果我们能窥视一下编译器内部,我们会发现这个级别的常量折叠非常简单,它是由前端执行的。  它不需要 丙烯酸羟乙酯vy提升力 后端优化器的。 因此,它总是 .  无论您是否请求优化(使用 /氧气 )或禁用优化(使用 /外径 )–这种优化总是会切入。

表达式有多复杂,但我们仍然在编译时将这些常量折叠在一起?  实际上,前端将处理几乎所有涉及常量的算术表达式(甚至是诸如 大小 ,如上所述,只要可以在编译时对它们求值)和运算符 + * / %% << >> ++ .  你甚至可以投进去 布尔 s、 逻辑运算符, 如果 s和 ?:

有没有经常折叠的情况 需要后端优化器的功能吗?  对。  考虑这个例子:

int bump(int n){返回n+1;}

int main(){return 3+bump(6);}

有了命令, cl/FA/Od应用程序cpp 上面写着“不用优化,谢谢” 和 键入App.asm ,我们得到:

压敏电阻     埃克斯,6 呼叫    ?颠簸@@YAHH@Z                           ; 碰撞 添加     eax,3个

正如我们所期望的:将6加载到ECX中——在x64调用约定中,它将第一个参数保存到我们的函数中 碰撞 .  那就打电话 碰撞 .  它的结果在EAX中返回。  最后,在EAX中添加3。

让我们看看如果我们请求优化会发生什么,使用: cl/FA/O2应用程序cpp

压敏电阻     eax,10个

在这里,后端优化器已经认识到 碰撞 函数非常小,所以它的主体应该简单地包含在调用程序中(我们将在本系列后面讨论一种称为“函数内联”的复杂优化)。  然后它意识到它可以在编译时对整个表达式求值,从而得到一条指令。  很不错吧?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享