编译器内联功能有多深?

时间:2011-09-18 17:09:26

标签: c++ function compiler-construction inline

假设我有一些函数,每个函数大约有两行简单的代码,他们互相称呼:A调用B调用C调用D。 ..来电K。 (所以基本上它是一系列很短的函数调用。)编译器通常会在调用树中有多深入内联这些函数?

2 个答案:

答案 0 :(得分:13)

这个问题没有意义。

如果你考虑内联及其后果,你会意识到这一点:

  • 避免函数调用(所有寄存器保存/帧调整)
  • 向优化器暴露更多上下文(死存储,死代码,常见子表达式消除......)
  • 复制代码(膨胀指令缓存和可执行文件大小等)

在决定是否内联时,编译器会在创建的潜在膨胀和预期的速度增益之间执行平衡操作。这种平衡行为受选项影响:对于gcc -O3意味着优化速度,而-Oz意味着优化尺寸,内联时它们具有准相反的行为!

因此,重要的不是“嵌套级别”,而是指令的数量(可能加权,因为并非所有指令都相等)。

这意味着一个简单的转发功能:

int foo(int a, int b) { return foo(a, b, 3); }
从内联的角度来看,

基本上是“透明的”。

另一方面,计算一百行代码的函数不太可能被内联。除了只调用一次的static个自由函数是准系统内联的,因为在这种情况下它不会产生任何重复。

从这两个例子中我们可以预见到启发式行为的表现:

  • 函数的指令越少,inling
  • 就越好
  • 调用次数越少,内联越好

之后,它们是您应该能够设置为以某种方式影响的参数(MSVC为__force_inline,强烈暗示inling,gcc,因为它们-finline-limit标记为“提高“指令数量等的门槛......”


在切线上:您知道部分内联吗?

它是在4.6中的gcc中引入的。顾名思义,这个想法是部分内联函数。大多数情况下,当函数被“保护”时可以避免函数调用的开销,并且可能(在某些情况下)几乎立即返回。

例如:

void foo(Bar* x) {
  if (not x) { return; } // null pointer, pfff!

  // ... BIG BLOC OF STATEMENTS ...
}

void bar(Bar* x) {
  // DO 1
  foo(x);
  // DO 2
}

可以“优化”为:

void foo@0(Bar* x) {
  // ... BIG BLOC OF STATEMENTS ...
}

void bar(Bar* x) {
  // DO 1
  if (x) { foo@0(x); }
  // DO 2
}

当然,内联的启发式再次适用,但它们更适用于歧视!


最后,除非您使用WPO(整个程序优化)或LTO(链接时间优化),否则只有在定义与调用站点相同的TU(翻译单元)时才能内联函数。

答案 1 :(得分:7)

我见过编译器内联超过5个函数。但在某些时候,它基本上变成了编译器所做的空间效率权衡。每个编译器在这方面都不同。 Visual Studio非常保守,内联。 GCC(在-O3下)和英特尔编译器喜欢内联......