何时使用内联函数,什么时候不使用内联函数?


184

我知道内联是对编译器的提示或请求,其用于避免函数调用的开销。

那么,在什么基础上可以确定一个函数是否适合内联呢?在这种情况下,应避免内联?


11
inline对C ++新手来说是什么CFLAGS,对Gentoo新手来说是什么:不,使用进行编译-O3 -funroll-loops -finline-functions不会使您的旧奔腾飞起来;)
Gregory Pakosz 2009年

1
不使用内联的原因是某些调试器不允许您设置断点或进入内联函数。
罗布·德弗里斯


5
您不应该确定是否应该内联函数。让编译器去做;它比您更好(并且可以根据每个调用的环境有选择地内联函数)。
David Thornley,2009年

@DavidThornley有时,即使设置了O3标志,如果定义在cpp文件中,编译器也不会内联该函数。因此,我遵循的经验法则是内联一个衬板以及那些没有任何循环的功能。
talekeDskobeDa

Answers:


209

避免函数调用的代价只是故事的一半。

做:

  • inline代替#define
  • 很小函数适合inline:更快的代码和更小的可执行文件(更多的机会保留在代码缓存中)
  • 该功能是小经常调用

别:

  • 大型函数:导致较大的可执行文件,无论调用开销导致执行得更快,都会严重影响性能
  • 受I / O约束的内联函数
  • 该功能很少使用
  • 构造函数和析构函数:即使为空,编译器也会为它们生成代码
  • 开发库时破坏二进制兼容性:
    • 内联现有功能
    • 更改内联函数或使内联函数变为非内联:库的先前版本调用旧的实现

在开发库时,为了使将来的类可扩展,您应该:

  • 即使正文为空,也添加非内联虚拟析构函数
  • 使所有构造函数都非内联
  • 编写复制构造函数和赋值运算符的非内联实现,除非无法通过值复制该类

请记住,inline关键字是对编译器的提示:编译器可以决定不内联函数,并且可以决定内联未标记的函数inline。我通常会避免标记功能inline(除了编写非常小的功能时)。

关于性能,明智的方法是(一如既往)分析应用程序,然后最终 inline一组代表瓶颈的功能。

参考文献:


编辑:Bjarne Stroustrup,C ++编程语言:

可以将一个函数定义为inline。例如:

inline int fac(int n)
{
  return (n < 2) ? 1 : n * fac(n-1);
}

inline说明符向编译器提示,编译器应尝试为fac()内联调用生成代码,而不是先为该函数放置代码,然后再通过通常的函数调用机制进行调用。聪明的编译器可以720为调用生成常量fac(6)。相互递归的内联函数,是否依赖于输入进行递归的内联函数等的可能性,使得无法保证inline实际上对函数的每次调用都是内联的。无法规范编译器的智能程度,因此一个编译器可能会生成720,另一个会生成,6 * fac(5)而另一个会进行非内联调用fac(6)

为了在没有异常聪明的编译和链接功能的情况下使内联成为可能,内联函数的定义(而不仅仅是声明)必须在范围内(第9.2节)。一个inlineespecifier不影响功能的语义。特别是,内联函数仍然具有唯一的地址,因此static内联函数的变量(第7.1.2节)也是如此。

EDIT2:ISO-IEC 14882-1998,7.1.2功能说明符

具有inline说明符的函数声明(8.3.5、9.3、11.4)声明内联函数。内联说明符向实现指示,在调用点处对函数体进行内联替换比通常的函数调用机制更好。在调用时不需要执行此内联替换的实现;但是,即使省略了此内联替换,仍应遵守7.1.2定义的其他内联函数规则。


34
inline不仅仅是对编译器的提示。它更改了有关多个定义的语言规则。同样,拥有静态数据也不是避免内联函数的铸铁理由。无论是否声明该函数,实现都必须为每个函数static分配一个静态对象inline。如果类具有内联构造函数和虚拟析构函数,则它们仍可扩展。空括号析构函数是一个虚拟函数,有时保持内联是一个好主意。
CB Bailey 2009年

2
从某种意义上说,该函数不一定以内联结尾(但是英语不是我的母语)。关于带有标记inline的函数中的静态函数,结果是该函数没有内联:您为调用付出了代价,并且包括并调用该函数的每个翻译单元都获得了自己的代码和静态变量的副本。开发库时不内联构造函数和析构函数的原因是与您的库的未来版本具有二进制兼容性
Gregory Pakosz 2009年

14
将其称为“编译器提示”是不准确的。实际上,inline如果编译器感觉非函数可以内联。而inline如果编译器决定不内联函数它们不会被内联。正如Charles Bailey所说,它改变了语言规则。与其将其视为优化提示,不如将其视为完全不同的概念更为准确。该inline关键字告诉编译器允许多个定义,而不是其他。“内联”优化几乎可以应用于任何功能,无论是否已标记inline
jalf

26
只是,当Stroustrup编写“内联说明符是对编译器的提示”时,我很惊讶自己被引用他而受到指责。无论如何,我花了足够的时间竭尽所能,以尽可能多的参考来支持此答案
Gregory Pakosz 2009年

2
@GregoryPakosz:但是inline,为了获得函数内联,我们并没有全部使用。有时我们想要其他好处,例如绕过ODR。
轨道轻度比赛

57

inline 与优化几乎没有关系。 inline是一个指令,指示编译器如果给定的函数定义在程序中多次出现,则不产生错误,并且保证该定义将在每次使用的转换中出现,并且在出现的任何位置都具有完全相同的定义。

给定以上规则,inline它适用于短函数,这些短函数的主体不需要在声明中仅包含需要的依赖项。每次遇到定义时,都必须对其进行解析,并且可能会生成其主体的代码,因此,这意味着在单个源文件中仅定义一次的函数上会产生一些编译器开销。

编译器可以内联(即用执行该功能操作的代码替换对该功能的调用)它选择的任何功能调用。过去的情况是,它“显然”无法内联一个未在与调用相同的转换单元中声明的函数,但是随着链接时间优化的使用不断增加,即使现在这不是事实。同样的事实是标记的功能inline可能无法内联。


我感到这比C ++的故意功能更多是巧合。这个想法与C语言中的“静态”全局变量非常相似。这是一个非常有趣的答案。我希望他们只是使用“内部”之类的关键字来表示内部链接。
Rehno Lindeque

+1。@Rehno:我不太确定你在说什么。链接与inline关键字有什么关系?什么是快乐的巧合?
jalf

@jalf:回想一下我的评论,我意识到它相当模糊,而且没有经过深思熟虑。在多个文件中定义相同的功能会导致链接器错误,可以通过将功能声明为“静态”来解决该错误。但是,“内联”允许您做相同的事情,但有细微的差别,因为它们实际上并没有像“静态”那样获得内部链接。我怀疑这实际上是一个巧合,因为语言实现者/设计者意识到他们将需要对头文件中声明的函数进行特殊处理,并将这些函数传递给“内联”。
Rehno Lindeque

4
不知道为什么您的评论获得了如此多的好评,因为性能是使用内联的主要原因。
gast128

10

告诉编译器内联函数是一种优化,最重要的优化规则是过早的优化是万恶之源。始终编​​写清晰的代码(使用高效的算法),然后分析程序并仅优化花费时间太长的函数。

如果您发现某个特定的函数非常简短,简单,并且在一个紧密的内部循环中被调用了成千上万次,则可能是个不错的选择。

但是,您可能会感到惊讶-许多C ++编译器会自动为您内联小函数-并且它们也可能会忽略您的内联请求。


实际上,我怀疑某些编译器会完全偷偷地完全忽略“内联”,而仅响应“ __inline”或“ __force_inline”。我想这是为了阻止虐待!
雷诺·林德克

通常情况并非如此。内联只是一个提示,但这是大多数编译器都认真对待的提示。您可以将编译器设置为发出汇编语言以及目标代码(/FAcs在Visual Studio中,-s在GCC中)以查看其功能。以我的经验,这两个编译器都非常重视inline关键字。
Crashworks于2009年

1
这很有趣,因为根据我的经验,g ++和VC都不是inline关键字。也就是说,如果您看到该函数已内联,并inline从中删除了说明符,它仍将内联。如果您有相反的具体例子,请分享!
帕维尔·米纳夫

4
inline关键字如何阻碍“清除代码”?“过早优化”中的关键字是过早的,而不是优化。说您应该积极*避免优化只是垃圾。引用的重点是您应该避免不必要的优化,并且会对代码产生有害的副作用(例如,使其难以维护)。我看不到inline关键字将如何使代码的可维护性降低,或者将其添加到函数中可能有害。
jalf

3
jalf,有时内联函数会使您的代码变慢,而不是变快。一个示例是从代码中多个不同位置调用该函数的情况。如果未内联函数,则从其他位置调用该函数时,它可能仍位于指令缓存中,并且分支预测变量可能已经预热。有些模式总是可以提高效率,因此使用它们永远不会受到伤害。内联不是其中之一。它通常对性能完全没有影响,有时会有所帮助,有时会有所伤害。我支持我的建议:首先进行个人简介,然后进行内联。
dmazzoni

5

找出答案的最佳方法是对程序进行概要分析,并标记被多次调用的小函数,并消耗CPU周期为inline。这里的关键字是“ small”(小)-与函数所花费的时间相比,函数调用的开销可以忽略不计,因此内联它们毫无意义。

我建议的另一种用法是,如果您有一些经常在性能关键代码中调用的小函数足以使高速缓存未命中,那么您也应该内联这些小函数。同样,它是探查器应该能够告诉您的。


4

过早的优化是万恶之源!

根据经验,我通常仅内联“ getters”和“ setters”。一旦代码正常运行并稳定下来,性能分析就可以显示哪些功能可以从内联中受益。

另一方面,大多数现代编译器都具有很好的优化算法,并且可以内联应该为您内联的内容。

恢复-编写内联一线函数,以后再担心其他函数。


2

内联函数可以消除将参数推入堆栈的需要,从而可以提高代码性能。如果有问题的函数位于代码的关键部分,则应在项目的优化部分中做出内联而非内联决策,

您可以在c ++常见问题中阅读有关内联的更多信息


1

我经常使用内联函数不是为了优化,而是使代码更具可读性。有时代码本身比注释,描述性名称等更短,更易于理解。例如:

void IncreaseCount() { freeInstancesCnt++; }

读者立即知道代码的完整语义。


0

我通常遵循一条经验法则,在该函数中以3-4个简单语句作为内联函数。但是要记住,这只是对编译器的提示。是否最终将其内联的最终调用仅由编译器进行。如果不止这些语句,我不会像笨的编译器那样声明内联,这可能会导致代码膨胀。


0

最好的方法是检查和比较生成的内联指令和非内联指令。但是,始终可以忽略inline。使用inline可能会导致您不想要的麻烦。


0

在决定是否使用内联时,我通常会牢记以下想法:在现代计算机上,内存延迟比原始计算可能是更大的瓶颈。众所周知,经常调用内联函数会增大可执行文件的大小。此外,这种功能可以存储在CPU的代码缓存中,当需要访问该代码时,它将减少缓存未命中的次数。

因此,您必须自己决定:内联会增大还是减小生成的机器代码的大小?调用该函数将导致高速缓存未命中的可能性有多大?如果在整个代码中都采用这种方式,那么我会说可能性很高。如果将其限制为单个紧密循环,则可能性很低。

在列出下面的情况下,我通常使用内联。但是,如果您真正关心性能,则必须进行性能分析。此外,您可能想检查编译器是否真正接受了提示。

  • 在紧密循环中调用的短例程。
  • 非常基本的访问器(获取/设置)和包装器功能。
  • 不幸的是,头文件中的模板代码会自动获取内联提示。
  • 像宏一样使用的短代码。(例如min()/ max())
  • 简短的数学例程。

0

此外,在维护大型项目时,内联方法具有严重的副作用。更改内联代码后,使用该代码的所有文件将由编译器自动重建(它是一个好的编译器)。这可能会浪费您的大量开发时间。

inline方法转移到源文件并且不再内联时,必须重新构建整个项目(至少这是我的经验)。以及将方法转换为内联时。


1
那是另一个问题。您会收到头文件中放置的代码的重建问题。无论是否标记inline,都没有关系(除了没有inline关键字之外,您会收到链接器错误-但inline关键字不是导致过度重建的问题。)
jalf

但是,与在文件文件中更改非内联方法相比,更改内联方法将导致过多的构建。
Thomas Matthews 2009年

0

仅当函数代码较小时,才应使用内联函数限定符。如果函数较大,则应首选常规函数,因为节省内存空间值得在执行速度上付出较小的代价。


0

当您认为自己的代码足够小以用作内联并且记得内联函数复制您的代码并将其粘贴到函数被调用时,这样可能足以增加您的执行时间,但同时也增加了内存消耗。当您使用循环/静态变量/递归/开关/转到/虚拟功能时,不能使用内联功能。虚拟方式要等到运行时和内联方式才能在编译期间进行,这样它们才能同时使用。


-2

我已经阅读了一些答案,看到缺少一些东西。

我使用的规则是不要使用内联,除非我希望它是内联的。看起来很傻,现在解释。

编译器足够聪明,短函数总是内联的。除非程序员说要这样做,否则永远不要将长函数作为内联函数使用。

我知道内联是对编译器的提示或请求

实际上inline是编译器的命令,它别无选择,并且after inline关键字使所有代码都内联。所以你永远不能使用inline关键字,编译器将设计最短的代码。

所以什么时候用 inline

如果要内联一些代码,请使用。我只知道一个例子,因为我只在一种情况下使用它。这是用户身份验证。

例如我有这个功能:

inline bool ValidUser(const std::string& username, const std::string& password)
{
    //here it is quite long function
}

无论此功能有多大,我都希望将其内联,因为它会使我的软件更难以破解。


2
内联仍然是一个提示。如果编译器认为您的函数过于肿,则可能无法内联。
Pete

一个人说内联是命令...另一个人说它是暗示有人会证实他的陈述,以便我们确定哪一个是正确的吗?

@ user2918461我支持内联语句只是一个提示。许多网站和书籍都对此提供了支持
WARhead
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.