我知道 inline 是对编译器的提示或请求,它用于避免函数调用开销。
那么在什么基础上可以确定一个函数是否是内联的候选者呢?在哪种情况下应该避免内联?
inline
对 C++ 新人来说就像 CFLAGS
对于 Gentoo 新人一样:不,用 -O3 -funroll-loops -finline-functions
编译不会让你的旧 Pentium 飞起来;)
避免函数调用的成本只是故事的一半。
做:
使用内联而不是#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
关键字有什么关系?什么是快乐的巧合?
-O3
添加到编译器标志,编译器就会自行确定要内联的内容。不要添加关键字并期望它们使您的代码更快。我听过一个关于优化的讲座,在 LLVM 工作的讲师说 inline 关键字与优化没有太大关系。这只是关于语义/语言规则
告诉编译器内联函数是一种优化,而最重要的优化规则是过早的优化是万恶之源。始终编写清晰的代码(使用高效的算法),然后分析您的程序并仅优化耗时过长的函数。
如果你发现一个特定的函数非常简短,并且在紧密的内部循环中被调用了数万次,那么它可能是一个很好的候选者。
不过,您可能会感到惊讶 - 许多 C++ 编译器会自动为您内联小函数 - 他们也可能会忽略您的内联请求。
/FAcs
,GCC 中的 -s
)以查看它的确切作用。根据我的经验,这两个编译器都非常重视 inline 关键字。
inline
关键字进行加权。也就是说,如果您看到函数被内联,并从中删除 inline
说明符,它仍然会被内联。如果你有任何相反的具体例子,请分享!
inline
关键字如何阻碍“清除代码”? “过早优化”中的关键字是premature,而不是优化。说你应该积极*避免优化只是垃圾。该引用的重点是您应该避免可能不必要的优化,并对代码产生有害的副作用(例如使其不易维护)。我看不出 inline
关键字会如何降低代码的可维护性,或者将其添加到函数中会产生怎样的危害。
过早优化是万恶之源!
根据经验,我通常只内联“getters”和“setters”。一旦代码工作并且稳定,分析可以显示哪些函数可以从内联中受益。
另一方面,大多数现代编译器都有很好的优化算法,并且会内联你应该为你内联的内容。
令人放心——编写内联的单行函数,以后再担心其他的。
找出答案的最佳方法是分析您的程序并将被多次调用并消耗 CPU 周期的小函数标记为 inline
。这里的关键字是“小”——一旦函数调用开销与函数花费的时间相比可以忽略不计,那么内联它们就没有意义了。
我建议的另一个用途是,如果您的小函数经常在性能关键代码中被调用以使缓存未命中相关,那么您可能也应该内联这些函数。同样,这是分析器应该能够告诉您的。
我经常使用内联函数不是为了优化,而是为了使代码更具可读性。有时代码本身比注释、描述性名称等更短且更易于理解。例如:
void IncreaseCount() { freeInstancesCnt++; }
读者立即知道代码的完整语义。
最好的方法是检查和比较生成的内联指令和非内联指令。但是,省略 inline
始终是安全的。使用 inline
可能会导致您不希望出现的麻烦。
仅当函数代码较小时才应使用内联函数限定符。如果函数较大,则应首选普通函数,因为节省内存空间值得牺牲相对较小的执行速度。
我通常遵循一个经验法则,即使用 3-4 个简单语句作为内联语句创建一个函数。但最好记住这只是对编译器的提示。使其内联或不内联的最终调用仅由编译器进行。如果语句多于这么多,我不会像愚蠢的编译器那样声明内联,这可能会导致代码膨胀。
在决定是否使用内联时,我通常会牢记以下想法:在现代机器上,内存延迟可能是比原始计算更大的瓶颈。众所周知,经常调用的内联函数会增加可执行文件的大小。此外,这样的函数可以存储在 CPU 的代码缓存中,这将在需要访问该代码时减少缓存未命中的数量。
因此,您必须自己决定:内联是增加还是减少生成的机器代码的大小?调用该函数导致缓存未命中的可能性有多大?如果它遍布整个代码,那么我会说可能性很高。如果它被限制在一个紧密的循环中,那么可能性就很低。
我通常在下面列出的情况下使用内联。但是,如果您真正关心性能,则分析是必不可少的。此外,您可能想检查编译器是否真的接受了提示。
在紧密循环中调用的短例程。
非常基本的访问器(get / set)和包装函数。
不幸的是,头文件中的模板代码会自动获得内联提示。
像宏一样使用的短代码。 (例如 min() / max())
简短的数学例程。
-O3
添加到编译器标志,编译器就会自行确定要内联的内容。不要添加关键字并期望它们使您的代码更快。我听过一个关于优化的讲座,在 LLVM 工作的讲师说 inline 关键字与优化没有太大关系。这只是关于语义/语言规则
此外,在维护大型项目时,内联方法具有严重的副作用。当内联代码改变时,所有使用它的文件都会被编译器自动重建(它是一个很好的编译器)。这可能会浪费您大量的开发时间。
当 inline
方法转移到源文件并且不再内联时,必须重新构建整个项目(至少这是我的经验)。以及当方法转换为内联时。
inline
无关紧要(除了没有 inline
关键字,您会收到链接器错误 - 但 inline
关键字不是导致过度重建的问题。
当您认为您的代码足够小可以用作内联并记住内联函数时,复制您的代码并粘贴它是调用函数,因此它可能足以增加您的执行时间,但也会增加内存消耗。当您使用循环/静态变量/递归/切换/goto/虚拟函数时,不能使用内联函数。虚拟意味着等到运行时,而内联意味着在编译期间它们不能同时使用。
我已经阅读了一些答案,发现缺少一些东西。
我使用的规则是不要使用内联,除非我希望它是内联的。看起来很傻,现在解释一下。
编译器足够聪明,短函数总是内联。并且永远不要将长函数作为内联函数,除非程序员说要这样做。
我知道 inline 是对编译器的提示或请求
实际上 inline
是编译器的命令,它没有选择,并且在 inline
关键字之后使所有代码内联。所以你永远不能使用 inline
关键字,编译器会设计最短的代码。
那么什么时候使用inline
呢?
如果您想内联一些代码,请使用。我只知道一个例子,因为我只在一种情况下使用它。是用户认证。
例如我有这个功能:
inline bool ValidUser(const std::string& username, const std::string& password)
{
//here it is quite long function
}
无论这个函数有多大,我都希望将它作为内联函数,因为它使我的软件更难破解。
inline
不仅仅是对编译器的提示。它改变了关于多个定义的语言规则。此外,拥有静态数据并不是避免内联函数的铁定理由。无论函数是否声明为inline
,实现都必须为每个静态函数分配一个静态对象。如果类具有内联构造函数和虚拟析构函数,它们仍然是可扩展的。空大括号析构函数是有时保留内联的一个好主意的虚函数。inline
的函数中的静态,结果是该函数没有被内联:您为调用付出了代价,并且每个包含和调用该函数的翻译单元都获得了自己的代码和静态变量副本。开发库时不内联构造函数和析构函数的原因是与库的未来版本的二进制兼容性inline
函数。如果编译器决定不内联inline
函数,它们也不会被内联。正如查尔斯贝利所说,它改变了语言规则。与其将其视为优化提示,不如将其视为完全不同的概念更为准确。inline
关键字告诉编译器允许多个定义,仅此而已。 “内联”优化几乎可以应用于任何函数,无论它是否标记为inline
。inline
来获得函数内联。有时我们想要其他好处,比如绕过 ODR。