ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

9
9 revs

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

做:

使用内联而不是#define

非常小的函数非常适合内联:更快的代码和更小的可执行文件(更多的机会留在代码缓存中)

该函数很小并且经常被调用

不:

大型函数:导致更大的可执行文件,无论调用开销导致执行速度如何,都会显着降低性能

I/O 绑定的内联函数

该功能很少使用

构造函数和析构函数:即使为空,编译器也会为它们生成代码

开发库时破坏二进制兼容性:内联现有函数 更改内联函数或使内联函数非内联:库的早期版本调用旧实现

内联现有函数

更改内联函数或使内联函数非内联:库的早期版本调用旧实现

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

即使主体为空,也添加非内联虚拟析构函数

使所有构造函数非内联

编写复制构造函数和赋值运算符的非内联实现,除非类不能按值复制

请记住,inline 关键字是对编译器的提示:编译器可能决定不内联函数,它可以决定内联最初未标记为 inline 的函数。我通常避免标记函数 inline(可能在编写非常非常小的函数时除外)。

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

参考:

内联或不内联

[9] 内联函数

C++ 的策略/二进制兼容性问题

GotW #33:内联

内联 Redux

有效的 C++ - 第 33 条:明智地使用内联

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

可以将函数定义为内联函数。例如:

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

inline 说明符是对编译器的提示,它应该尝试为调用 fac() inline 生成代码,而不是为函数编写一次代码,然后通过通常的函数调用机制进行调用。聪明的编译器可以为调用 fac(6) 生成常量 720。内联函数相互递归的可能性,递归或不依赖于输入的内联函数等,使得无法保证内联函数的每次调用实际上都是内联的。编译器的聪明程度无法立法,因此一个编译器可能会生成 720、另一个 6 * fac(5) 和另一个未内联的调用 fac(6)。为了在没有异常聪明的编译和链接工具的情况下使内联成为可能,内联函数的定义(而不仅仅是声明)必须在范围内(第 9.2 节)。内联说明符不会影响函数的语义。特别是,内联函数仍然具有唯一的地址,内联函数的静态变量(第 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。
C
CB Bailey

inline 与优化关系不大。 inline 指示编译器在给定定义的函数在程序中多次出现时不产生错误,并承诺该定义将在每次使用它的翻译中出现,并且在任何出现的地方都将准确地包含相同的定义。

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

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


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

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

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

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


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

过早优化是万恶之源!

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

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

令人放心——编写内联的单行函数,以后再担心其他的。


T
Timo Geusch

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

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


d
danatel

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

void IncreaseCount() { freeInstancesCnt++; }

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


w
wallyk

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


j
jball

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

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


n
nitish

仅当函数代码较小时才应使用内联函数限定符。如果函数较大,则应首选普通函数,因为节省内存空间值得牺牲相对较小的执行速度。


N
Naveen

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


R
Rehno Lindeque

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

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

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

在紧密循环中调用的短例程。

非常基本的访问器(get / set)和包装函数。

不幸的是,头文件中的模板代码会自动获得内联提示。

像宏一样使用的短代码。 (例如 min() / max())

简短的数学例程。


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

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

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


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

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


S
ST3

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

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

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

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

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

那么什么时候使用inline呢?

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

例如我有这个功能:

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

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


inline 仍然是一个提示。如果编译器认为您的函数过于臃肿,它可能无法内联。
一个说 inline 是一个命令……另一个说它是一个提示 有人会证实他的陈述,以便我们确定哪一个是真的吗?
@user2918461 我支持内联语句只是一个提示。这已得到许多网站和书籍的支持