【问题标题】:When to use inline function and when not to use it?什么时候使用内联函数,什么时候不使用它?
【发布时间】:2010-12-28 06:53:14
【问题描述】:

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

那么在什么基础上可以确定一个函数是否是内联的候选者呢? 在哪种情况下应该避免内联?

【问题讨论】:

  • inline 对于 C++ 新手来说就像 CFLAGS 对于 Gentoo 新手一样:不,用 -O3 -funroll-loops -finline-functions 编译不会让你的旧 Pentium 飞起来;)
  • 不使用内联的原因是某些调试器不允许您设置断点或进入内联函数。
  • 你不应该决定一个函数是否应该被内联。让编译器来做;它比你更擅长(并且可以根据每次调用的环境有选择地内联函数)。
  • @DavidThornley 有时,即使设置了 O3 标志,如果定义在 cpp 文件中,编译器也不会内联函数。所以,我遵循的经验法则是内联一个衬里以及那些没有任何循环的函数。

标签: c++ c inline


【解决方案1】:

避免函数调用的成本只是故事的一半。

做:

  • 使用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() 生成代码,而不是为函数编写一次代码,然后通过通常的函数调用机制进行调用。聪明的编译器可以为调用fac(6) 生成常量720。相互递归的内联函数、递归或不依赖于输入的内联函数等的可能性使得无法保证inline 函数的每次调用实际上都是内联的。无法对编译器的聪明程度进行立法,因此一个编译器可能会生成720、另一个6 * fac(5),以及另一个未内联的调用fac(6)

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

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

带有inline 说明符的函数声明(8.3.5、9.3、11.4)声明了一个内联函数。 inline 说明符向实现表明,在调用点对函数体进行内联替换优于通常的函数调用机制。在调用点执行此内联替换不需要实现;但是,即使省略了这个内联替换,7.1.2 中定义的内联函数的其他规则仍应遵守。

【讨论】:

  • inline 不仅仅是对编译器的提示。它改变了关于多个定义的语言规则。此外,拥有静态数据并不是避免内联函数的铁定理由。无论函数是否声明为inline,实现都必须为每个静态函数分配一个静态对象。如果类具有内联构造函数和虚拟析构函数,它们仍然是可扩展的。空括号析构函数是有时保留内联的好主意的一个虚函数。
  • 这是一个提示,因为函数不一定最终内联(但英语不是我的母语)。关于标记为inline 的函数中的静态变量,结果是该函数没有被内联:您为调用付出了代价,并且每个包含和调用该函数的翻译单元都获得了自己的代码和静态变量副本。开发库时不内联构造函数和析构函数的原因是与库的未来版本的二进制兼容性
  • 将其称为“编译器提示”是不准确的。实际上,如果编译器愿意,可以内联非inline 函数。如果编译器决定不内联 inline 函数,则不会内联它们。正如查尔斯贝利所说,它改变了语言规则。与其将其视为优化提示,不如将其视为完全不同的概念更为准确。 inline 关键字告诉编译器允许多个定义,仅此而已。 “内联”优化几乎可以应用于任何函数,无论它是否标记为inline
  • 只是,当 Stroustrup 写“内联说明符是对编译器的提示”时,我很惊讶我被指责引用了他的话。无论如何,我花了足够的时间尽我所能用尽可能多的参考资料来支持这个答案
  • @GregoryPakosz:但我们并不是都使用inline 来获得函数内联。有时我们想要其他好处,例如绕过 ODR。
【解决方案2】:

inline 与优化关系不大。 inline 是对编译器的一条指令,如果给定的函数在程序中多次出现,则不会产生错误,并承诺该定义将出现在每次使用它的翻译中,并且在任何出现的地方都会出现相同的定义。

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

编译器可以内联(即用执行该函数的该操作的代码替换对该函数的调用)它选择的任何函数调用。过去的情况是,它“显然”无法内联未在与调用相同的翻译单元中声明的函数,但随着链接时间优化的使用越来越多,即使现在这不是真的。同样正确的是标记为inline 的函数可能不会被内联。

【讨论】:

  • 我觉得这与其说是 C++ 的有意特性,倒不如说是一个快乐的巧合。这个想法与 C 中的“静态”全局变量非常相似。不过,这是一个非常有趣的答案。我希望他们只是使用像“内部”这样的关键字来表示内部链接。
  • +1。 @Rehno:我不太确定你在说什么。链接与inline 关键字有什么关系?什么是幸福的巧合?
  • @jalf:回想起来阅读我的评论,我意识到它相当模糊,也没有经过深思熟虑。在多个文件中定义相同的函数会导致链接器错误,可以通过声明函数“静态”来解决该错误。但是,“内联”允许您做同样的事情,但它们实际上并没有像“静态”那样获得内部链接。我怀疑这实际上更像是一个巧合,因为语言实现者/设计者意识到他们需要对头文件中声明的函数做一些特殊的事情,并将其转移到“内联”。
  • 不知道为什么您的评论获得了如此多的赞成票,因为性能是使用内联的主要原因。
  • @gert128 “过早的优化是万恶之源”如果您担心性能,只需将-O3 添加到编译器标志,编译器就会自行确定要内联的内容。不要添加关键字并期望它们使您的代码更快。我听过一个关于优化的讲座,在 LLVM 工作的讲师说 inline 关键字与优化没有太大关系。这只是关于语义/语言规则
【解决方案3】:

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

如果您发现某个特定函数非常简短,并且在紧密的内部循环中被调用了数万次,那么它可能是一个不错的候选者。

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

【讨论】:

  • 确实,我怀疑某些编译器会偷偷地完全忽略“内联”,只响应“__inline”或“__force_inline”。我想这是为了阻止滥用!
  • 通常情况并非如此。 inline 只是一个提示,但它是大多数编译器认真对待的提示。您可以将编译器设置为发出汇编语言以及目标代码(Visual Studio 中的/FAcs,GCC 中的-s)以查看它的确切作用。根据我的经验,这两个编译器都非常重视 inline 关键字。
  • 这很有趣,因为根据我的经验,g++ 和 VC 都不会对 inline 关键字产生影响。也就是说,如果您看到函数被内联,并从中删除 inline 说明符,它仍然会被内联。如果您有任何相反的具体例子,请分享!
  • inline 关键字如何阻碍“清除代码”? “过早优化”中的关键字是premature,而不是优化。说你应该积极*避免优化只是垃圾。该引用的重点是您应该避免可能不必要的优化,并对代码产生有害的副作用(例如使其不易维护)。我看不出inline 关键字会如何降低代码的可维护性,或者将其添加到函数中会产生怎样的危害。
  • jalf,有时内联函数会使你的代码变慢,而不是变快。一个示例是从代码中的多个不同位置调用该函数时;如果该函数没有内联,那么当它从不同的地方调用时它可能仍然在指令缓存中,并且分支预测器可能已经预热。有一些模式总是能提高效率,所以使用它们永远不会有坏处。内联不是其中之一。它通常对性能完全没有影响,有时会有所帮助,有时会造成伤害。我支持我的建议:先配置文件,然后内联。
【解决方案4】:

过早的优化是万恶之源!

根据经验,我通常只内联“getter”和“setter”。一旦代码工作并且稳定,分析可以显示哪些函数可以从内联中受益。

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

Reasuming -- 编写内联的单行函数,以后再担心其他的。

【讨论】:

    【解决方案5】:

    找出答案的最佳方法是分析您的程序并将被多次调用并消耗 CPU 周期的小函数标记为 inline。这里的关键字是“小”——一旦函数调用开销与函数花费的时间相比可以忽略不计,那么内联它们就没有意义了。

    我建议的另一个用途是,如果您的小函数在性能关键代码中经常被调用以使缓存未命中相关,那么您也应该内联这些函数。同样,这是分析器应该能够告诉您的。

    【讨论】:

      【解决方案6】:

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

      void IncreaseCount() { freeInstancesCnt++; }
      

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          内联函数可能通过消除将参数压入堆栈的需要来提高代码性能。 如果有问题的函数位于代码的关键部分,您应该在项目的优化部分做出内联而不是内联的决定,

          您可以在c++ faq 中阅读有关内联的更多信息

          【讨论】:

            【解决方案9】:

            只有在函数代码较小的情况下才应该使用内联函数限定符。如果函数较大,您应该更喜欢普通函数,因为节省内存空间值得在执行中做出相对较小的牺牲速度。

            【讨论】:

              【解决方案10】:

              我通常遵循一个经验法则,即使用 3-4 个简单语句作为内联语句创建一个函数。但最好记住这只是对编译器的提示。使其内联或不内联的最终调用仅由编译器进行。如果语句多于这么多,我不会像愚蠢的编译器那样声明内联,这可能会导致代码膨胀。

              【讨论】:

                【解决方案11】:

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

                因此,您必须自己决定:内联会增加还是减少生成的机器代码的大小?调用该函数导致缓存未命中的可能性有多大?如果它遍布整个代码,那么我会说可能性很高。如果它被限制在一个紧密的循环中,那么可能性就很低了。

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

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

                【讨论】:

                • “过早的优化是万恶之源”,如果您担心性能,只需将-O3 添加到编译器标志,编译器就会自行确定要内联的内容。不要添加关键字并期望它们使您的代码更快。我听过一个关于优化的讲座,在 LLVM 工作的讲师说 inline 关键字与优化没有太大关系。这只是关于语义/语言规则
                【解决方案12】:

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

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

                【讨论】:

                • 这是一个不同的问题。对于放置在头文件中的代码,您会遇到重建问题。是否标记为inline 无关紧要(除了没有inline 关键字,您会收到链接器错误 - 但inline 关键字不是导致过度重建的问题。
                • 但是,与在 shource 文件中更改非内联方法相比,更改内联方法会导致构建过多。
                【解决方案13】:

                当您认为您的代码足够小可以用作内联并记住内联函数时,复制您的代码并粘贴它是调用函数,这样可能足以增加您的执行时间,但也会增加内存消耗。 当您使用循环/静态变量/递归/切换/goto/虚拟函数时,不能使用内联函数。 虚拟意味着等到运行时,而内联意味着在编译期间它们不能同时使用。

                【讨论】:

                  【解决方案14】:

                  我已经阅读了一些答案,发现有些东西丢失了。

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

                  编译器足够聪明,短函数总是内联。并且永远不要将长函数作为内联函数,除非程序员要求这样做。

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

                  其实inline是编译器的命令,它没有选择,在inline关键字之后使所有代码内联。所以你永远不能使用inline关键字,编译器会设计最短的代码。

                  那么什么时候使用inline

                  如果你想有一些内联代码使用。我只知道一个例子,因为我只在一种情况下使用它。是用户认证。

                  例如我有这个功能:

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

                  无论这个函数有多大,我都希望将它作为内联函数,因为它使我的软件更难破解。

                  【讨论】:

                  • inline 仍然是一个提示。如果编译器认为你的函数过于臃肿,它可能无法内联。
                  • 一个说 inline 是一个命令...另一个说它是一个提示 有人会证实他的说法,以便我们确定哪一个是真的吗?
                  • @user2918461 我支持内联语句只是一个提示。这已得到许多网站和书籍的支持
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-11-10
                  相关资源
                  最近更新 更多