【问题标题】:__inline functions vs normal functions in C__inline 函数与 C 中的普通函数
【发布时间】:2012-04-12 19:26:43
【问题描述】:

我正在研究宏,发现了很多关于宏和内联函数之间区别的资料和问题。但是没有什么可以具体说明和区分内联函数与普通函数的优缺点。

但是如果我想在普通函数和内联函数之间进行选择呢?

我知道使用内联函数会增加代码大小。但是,虽然研究规模不是主要问题,但效率是目标。使函数成为内联函数意味着对函数的调用尽可能快。(由于堆栈和填充开销)

总是使用内联函数好还是不好?如果不是,那么为什么?与内联相比,使用普通函数有什么好处?

在阅读其他问题时,我读到内联只是对编译器的提示。编译器可能会忽略它。编译器何时会忽略它以及为什么

【问题讨论】:

  • 有些函数不能内联。例如,递归函数。
  • 没有黄金法则。有时内联更好(以保存函数调用和堆栈操作),有时它不值得(代码大小增加太多)。你不会找到The One True Answer,它不存在。
  • 很好,其他人呢。因为我很少使用递归函数。
  • 编译器内联的决定通常使用heuristic来决定。
  • 顺便说一句,对于带有inline 标记的函数必须发生的事情,当它的定义 出现在多个编译单元中时,它不会产生链接器冲突。所以你可以安全地把它放在头文件中。从这个意义上说,inline 使得一个函数在多个单元中内联成为可能

标签: c inline c99


【解决方案1】:

内联函数有几个优点:

  1. 它可以使程序大小更小。这通常是一个函数只使用一次的情况。另见 2. 和 3.

  2. 如果编译器知道变量是常量或非 NULL 或类似的东西,编译器可以删除函数的未使用位。这可以节省大小,但也可以使代码在运行时更高效。

  3. 编译器可以消除调用函数的位,甚至其他内联函数,因为它可以看到函数对数据的作用。 (假设代码检查返回值并在它为 NULL 时调用错误函数,它可能能够排除这种情况。

  4. 它可以减少调用开销,但在当前具有预测分支的处理器中,这并没有你想象的那么好。

  5. 它可以从循环中提升常量位,进行公共子表达式消除,以及许多其他优化以提高循环代码的效率,等等。

还有缺点:

  1. 显然可以让代码变大。

  2. 它会增加调用函数中的寄存器压力,这可能会混淆编译器并阻止其优化。

  3. 拥有一个可以驻留在 CPU 缓存中的热函数比将其复制到许多并不总是缓存的地方要快。

  4. 它会妨碍调试。

内联函数只是一个提示的原因主要是因为 C 标准根本不要求编译器优化任何东西。如果它不是一个提示,那么优化就不是可选的。此外,仅仅因为函数 没有 标记为内联并不会阻止编译器内联它,如果它计算出这样做是有利的。

【讨论】:

  • +1 当函数未标记为内联时,关于编译器内联的最后一条评论。有关 GCC 的作用(例如)的信息,请参阅 gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html。大多数时候,我不理会inline 提示,只是在编译器认为合适的时候使用 Makefile 中的优化标志来内联。
【解决方案2】:

如果编译器认为性能会更好,通常会选择内联。因此,它必须比较实际调用非内联函数(将参数推入堆栈、保存寄存器等)的成本,而不是代码本身。所以对于简单的访问器,或者简单的计算,调用函数的成本往往更高,因此编译器会内联。

通常编译器也有大小限制,因此它们不允许内联过多地增加代码。

还有地方要考虑。内联函数不太可能导致页面错误,也可能不会导致处理器缓存未命中,因此从这个角度来看通常会更快。非内联函数通常需要将代码加载到处理器缓存中,相比之下这非常慢。

将函数声明为内联还有其他原因,即使您并不真正期望编译器内联它们。 C++ 中的整个 STL 都是内联的,尽管其中大部分实际上不太可能是内联的。这样做是出于模板实例化的原因,以及与应用程序二进制接口有关的问题等等。

【讨论】:

  • 很好,我认为你是内联的。但是“通常编译器有大小限制”——你能详细说明一下吗?我认为大小限制适用于操作系统或系统而不是编译器
  • 如果您愿意,请详细说明尺寸限制部分,我认为 Brett 的评论是我的答案。
  • 编译器有一组选项用于控制可以内联的大小函数。例如,--param max-inline-insns-single=<num> 控制仅从一处调用的函数的内联。有好几个这样的。
【解决方案3】:

内联函数的好处是消除了函数调用开销,并使优化器有更好的机会使用函数代码优化调用代码。例如,优化器在某些情况下可能会完全消除内联代码。

缺点是可能会增加代码大小,如果瓶颈是 CPU 指令获取,在某些情况下也会降低执行速度。

所以这取决于。编译器使用各种启发式方法来确定内联函数是否是净收益。即使您没有指定 inline 关键字,编译器也可能内联函数,或者如果您这样做,它可能不会内联。通常有一些方法可以强制编译器内联函数以覆盖其决定。

这个What are good heuristics for inlining functions? 有很好的讨论和关于内联启发式的更多链接。

【讨论】:

  • 我不明白你为什么要重复我已经提到过的事情
  • @knoxxs 责备那些试图帮助你的人并不是一个好政策。
  • @knoxxs:我不是在重复你说的话。我还说代码增加可能会导致性能下降,因此内联不仅仅是代码大小。它可能会增加或减少代码大小,也可能会增加或降低性能。
【解决方案4】:

当你使用内联函数时,函数代码在被调用时粘贴在行中,它生成的可执行程序也比不使用内联函数的程序大,另一方面,没有内联函数,当被调用时,程序停止并跳转到函数代码开始的内存目录...

结论: 内联函数 = 更好的性能,更多的空间 funcion = 更少的空间,略低的性能

【讨论】:

  • 我已经提到过..我要求任何其他差异,如果存在的话。
【解决方案5】:

GNU GCC 对 gcc 的内联选项和决策过程进行了很好的概述。

这确定了不能进行内联的情况:“使用可变参数、使用 alloca、使用可变大小的数据类型、使用计算的 goto、使用非本地 goto 和嵌套函数”

重要的一点是一个非静态函数,即一个带有extern 链接的函数(即每个声明没有static 的函数)可以被调用,或者将其地址放在不同的源文件中。

这会强制编译器生成“普通”函数以及任何内联函数体。当然,这会生成一个比仅生成“普通”非内联函数更大的程序。

Gcc 有一个选项-ffunction-sections,它生成目标文件,使链接器能够消除未使用的函数,但代价是“...汇编器和链接器将创建更大的目标文件和可执行文件,而且速度也会变慢。”

在较新版本的 gcc 上,支持链接时间优化 (LTO)(请参阅 "Whole Program Optimizations")。这允许优化阶段查看所有已编译的程序,并进行更积极的内联和优化,并排除未使用的代码。

看看一些较新的gcc-4.6gcc-4.7 过程间优化也很有趣。例如,仅内联内联函数的“热路径”,而不是整个函数。 Gcc 也可能生成一个函数的多个实例,因为常量是已知的,并且 gcc 计算出最好有多个实现,每个实现都针对那些已知的常量进行优化。

gcc 中有一些选项可以要求编译器内联所有“足够简单”的函数。

最后它说“根据 ISO C++ 的要求,GCC 认为定义在类主体中的成员函数被标记为内联,即使它们没有使用 inline 关键字显式声明”。在这种情况下,使用“足够简单”的规则。

总结:编译器可能会做非常聪明的优化,超出普通函数或内联函数的预期。

【讨论】:

  • 什么是-“非静态函数可能会被占用”。我已经搜索过,但一无所获。你有什么建议吗
  • 所有声明没有单词static的函数都是非静态的。这意味着该函数是extern 链接,因此在文件(编译单元)之外对程序的所有其余部分都是可见的。这意味着编译器必须生成一个“正常”函数,以防该函数在其他地方被调用,或者甚至某些东西需要它的地址(因此不需要调用它)。如果函数从未被调用,链接器可能会消除生成的 extern 函数,并且它的地址没有被占用。
  • 只有当代码用-ffunction-sections编译并用--gc-sections链接时,链接器才能消除函数。编译器选项-fwhole-program-flto 也能够消除未使用的外部函数(更不用说其他C 文件中的内联函数)。
  • @ams - 是的,值得添加。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 1970-01-01
  • 2018-10-24
  • 1970-01-01
  • 2019-01-17
  • 2010-12-24
相关资源
最近更新 更多