阅读 GCC 代码注释和在线文档,似乎有两种类型的内联程序 - 早期内联程序和 IPA 内联程序。例如,在 gcc/ipa-fnsummary.c
/* 当针对 IPA 内联进行优化和分析时,初始化循环优化器,以便我们可以生成正确的内联提示。
在优化和分析早期内联时,初始化节点参数,以便我们可以生成正确的 BB 谓词。*/
这两种内联是什么?两者有什么区别?
阅读 GCC 代码注释和在线文档,似乎有两种类型的内联程序 - 早期内联程序和 IPA 内联程序。例如,在 gcc/ipa-fnsummary.c
/* 当针对 IPA 内联进行优化和分析时,初始化循环优化器,以便我们可以生成正确的内联提示。
在优化和分析早期内联时,初始化节点参数,以便我们可以生成正确的 BB 谓词。*/
这两种内联是什么?两者有什么区别?
简单的说:
编译单个文件时,早期的内联器在单个源文件级别上运行。它将仅在编译的源文件及其包含的头文件(单个编译单元的范围)范围内内联函数。
在整个程序优化期间,IPA 内联器在链接时间上运行。它在激活-flto
选项时发生,代表链接时间优化。
当-flto
指定时,gcc 将中间程序表示,称为 GIMPLE 树,嵌入到每个目标文件的专门部分中。稍后,链接时间优化器(GCC 的lto1
可执行文件)读取此信息,并执行不同的优化过程,包括 IPA 内联程序,以生成最终优化的可执行文件。
两个内联的影响可以用一个简单的例子来说明:
// foo.h
void foo() {}
// goo.h
int goo();
// goo.cpp
#include "goo.h"
int goo() { return 0x123; }
// foo.cpp
#include "foo.h"
#include "goo.h"
int main()
{
foo();
return goo();
}
首先,通常的-O3
编译:
g++ -O3 foo.cpp goo.cpp
通过反汇编a.out
( objdump a.out -d
) 我们得到以下代码main
:
00000000000004f0 <main>:
4f0: e9 0b 01 00 00 jmpq 600 <_Z3goov>
4f5: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4fc: 00 00 00
4ff: 90 nop
对的调用foo()
消失了——这是早期内联的工作。然而,函数goo()
在编译期间对编译器不可见foo.cpp
,因此无法对其进行优化。
现在,重复编译-flto
:
g++ -O3 -flto foo.cpp goo.cpp
我们将得到以下反汇编:
00000000000004f0 <main>:
4f0: b8 23 01 00 00 mov $0x123,%eax
4f5: c3 retq
4f6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4fd: 00 00 00
这一次,调用goo
被内联并替换为它的结果0x123
——这是 IPA 内联的工作。
根据 ipa-inline.c 中的内部文档,早期内联是一个简单的本地内联传递,它仅基于本地属性在当前函数中内联被调用者。此通道的主要优势在于它能够消除大多数 C++ 代码中存在的抽象损失,并为更高级的过程间分析 (IPA) 准备代码。
IPA 内联程序是基于 IPA 期间收集的信息的更高级的内联程序。由于它有更多信息,它可以更好地估计哪些被调用者对内联最有利。它还将修剪调用图并删除所有调用站点都已内联的函数。
有关更多信息,请参阅ipa-inline.c的内部文档