如果你已经到了这个博客系列的中间,你可能想从最开始 开始 .
这篇文章探讨了常量折叠——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个
在这里,后端优化器已经认识到 碰撞 函数非常小,所以它的主体应该简单地包含在调用程序中(我们将在本系列后面讨论一种称为“函数内联”的复杂优化)。 然后它意识到它可以在编译时对整个表达式求值,从而得到一条指令。 很不错吧?