优化C++代码综述

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

null

这篇文章解释了VisualC++编译器中的数据流——从我们的C++源程序开始,并用相应的二进制程序结束。这篇文章是 一个简单的方法是把我们的脚趾伸进海洋的浅水区。

让我们检查一下编译一个存储在 应用程序.cpp ,从命令行(如果您要从visualstudio内部启动编译,下面的图表必须包含更高层次的软件;然而,它们最终会发出我将要描述的命令)。

让我们想象一下我们刚刚输入: CL/O2应用程序cpp

这个 代表“编译和链接”。 /氧气 告诉编译器优化速度—生成运行速度尽可能快的机器代码。此命令启动一个进程来执行 CL.EXE文件 程序——一个调用多个软件的“驱动程序”:当它们连接在一起时,它们处理我们系统中的文本 应用程序.cpp 最终生成一个二进制文件,称为 应用程序.exe . 执行时,这个二进制文件将执行我们在原始源文件中指定的操作。

图片[1]-优化C++代码综述-yiteyi-C++库

让我们绕着上面的图表转一圈,解释一下发生了什么。

CL.EXE文件 解析我们的命令行,并检查它是否有意义。然后调用C++的“前端”,位于 C1XX.DLL (“CXX”的意思是表示“C++”,但可以追溯到“+”在文件名中不是合法字符的时候)。这个 前端 是链条的一部分 理解 C++语言。它扫描、解析和转换你的 应用程序.cpp 通过5个临时文件传递到下一个组件。这5个文件所代表的“语言”称为“CIL”,即“C中间语言”。不要将此与中间代码混淆,中间代码由托管语言(如C#)生成,有时称为“MSIL”,但不幸的是,在 ECMA-335型 标准。

接下来, CL.EXE文件 调用“后端”,位于 C2.DLL文件 . 我们称后端为“UTC”,它代表“通用元组编译器”,尽管这个名称在visualstudio中包含的任何二进制文件中都没有显示。后端首先将前端的信息转换为元组(二进制指令流)。如果我们要显示它们,它们看起来就像一种高级汇编语言。从某种意义上说:

  • 操作是通用的。例如,一个 分支(LE) 指令与最终如何翻译成 化学机械抛光 x64机器代码中的指令
  • 操作数是符号的。例如, t66型 表示由编译器生成的临时变量,与 eax公司 ,在运行时保存其值的x64寄存器

因为我们要求编译器通过 /氧气 在switch中,后端的优化部分分析元组并将它们转换为一种执行速度更快但在语义上等价的形式,从而产生与原始元组相同的结果。在这一步之后,元组被传递到后端的CodeGen部分,后者决定要发出的最终二进制代码。

CodeGen模块发出 应用对象 作为磁盘上的文件。最后,链接器使用该文件,解析对库的任何引用,并生成最终结果 应用程序.exe 二元的。

在图中,黑色箭头显示数据流-文本或二进制。 红色 箭头表示控制流。

(我们将在本系列后面的部分返回此图,当我们谈到整个程序优化的主题时,使用 /德国劳埃德船级社 切换,并使用 /LTCG公司 开关。我们将看到相同的一组框,但以不同的方式连接)

总而言之:

  • 这个 前端 理解 C++源代码。链的其他部分(后端和链接器)与原始源语言的细节(大部分)是隔离的。它们处理上面提到的元组,形成一种高级二进制汇编语言。原始源程序可能是任何命令式语言,如FORTRAN或Pascal。后端真的不在乎。嗯,不多。
  • 这个 优化 后端的一部分将元组转换为运行速度更快的更高效的形式。这种转换,我们称之为优化(我们真的应该称之为“改进”,因为可能有 其他 改进可以产生更快的运行代码——我们的目标就是接近这个理想。然而,几十年前,有人创造了“优化”这个词,我们被它困住了)这样的优化有几十种,名字有“常数折叠”、“公共子表达式消除”、“提升”、“不变代码运动”、“死代码消除”、“函数内联”、“自动矢量化”,在大多数情况下,这些优化独立于程序将在其上执行的最终处理器—它们是“独立于机器”的优化。
  • 这个 编码基因 后端的一部分决定如何布局运行时堆栈(用于实现函数“激活帧”);如何充分利用现有的机器寄存器;添加实现函数调用约定的细节;并利用其对目标机器的详细了解,转换代码以使其运行得更快(举一个小例子,如果您查看汇编代码(例如,在调试代码时使用visualstudio(Alt+8)中的反汇编窗口),您可能会注意到如下指令 异或eax,eax 用于将EAX设置为零,优先于更明显的 移动eax,0 . 为什么?因为XOR形式更小(2字节而不是5字节),执行速度更快。我们不妨称之为“微优化”,并怀疑这是否值得费心。回想一下这句谚语:“管好一分钱,英镑就会管好自己”)。与 优化 部分, 编码基因 知道要在其上运行代码的处理器体系结构。  在某些情况下,它甚至会根据对目标处理器的理解来改变机器指令的排列顺序——一个称为“调度”的过程。   哦,我最好再解释一下: 编码基因 知道 无论是针对x86、x64还是ARM-32;但这种情况很少见 知道 代码将在特定的微体系结构上运行——例如,Nehalem和Sandy Bridge(看到了吗/favor:ATOM switch 对于它知道更多细节的情况)

这个博客将关注 优化 编译器的一部分,很少提及构成链的其他组件- 前端 , 编码基因 链接器 .

这篇文章介绍了许多行话。我不想让你完全理解这一点:这篇文章是一个概述,散布着一些想法,希望能激起你的兴趣,并确保你下次回来,我将开始解释所有的行话。

下一次,我们将看看最简单的优化以及它是如何工作的——一个叫做“常数折叠”的优化。

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