内联函数在C ++中的好处?
在C ++中使用内联函数的优点/缺点是什么?我看到它只会提高编译器输出的代码的性能,但是使用当今优化的编译器,快速的CPU,巨大的内存等(不像1980年那样,当时内存不足,所有内容都必须容纳100KB内存),这是什么呢?他们今天真的有优势吗?
解决方案
内联函数速度更快,因为我们不需要像往常那样将参数压入和弹出堆栈和返回地址;但是,它的确会使二进制文件稍大。
它有很大的不同吗?对于大多数人来说,在现代硬件上还不够明显。但这可以有所作为,对某些人来说就足够了。
将内容标记为内联并不能保证将其内联。这只是对编译器的建议。有时,例如当我们具有虚函数或者涉及递归时,这是不可能的。有时,编译器只是选择不使用它。
我可以看到这样的情况产生了明显的变化:
inline int aplusb_pow2(int a, int b) { return (a + b)*(a + b) ; } for(int a = 0; a < 900000; ++a) for(int b = 0; b < 900000; ++b) aplusb_pow2(a, b);
内联是对编译器的建议,可以随意忽略。它是少量代码的理想选择。
如果函数是内联的,则基本上是将其插入到对其进行函数调用的代码中,而不是实际调用单独的函数。这可以提高速度,因为我们不必进行实际的通话。
它还可以帮助CPU进行流水线操作,因为它们不必使用调用引起的新指令来重新加载流水线。
唯一的缺点是可能会增加二进制大小,但是,只要函数很小,这不会太大。
如今,我倾向于将这类决策留给编译器(无论如何,还是明智的决策)。编写它们的人往往对底层体系结构有更详细的了解。
好处
- 通过在需要的地方内联代码,程序将在函数调用和返回部件上花费更少的时间。它应该使代码运行得更快,即使它变得更大(请参见下文)。内联琐碎的访问器可能是有效内联的一个示例。
- 通过将其标记为内联,可以将函数定义放在头文件中(即,它可以包含在多个编译单元中,而无需链接程序抱怨)
缺点
- 它可以使代码更大(即,如果对非平凡函数使用内联)。这样,它可能会引起分页并破坏编译器的优化。
- 它会稍微破坏封装,因为它公开了对象处理的内部(但随后,每个"私有"成员也会)。这意味着我们不能在PImpl模式中使用内联。
- 它稍微破坏了封装2:C ++内联在编译时已解决。这意味着,如果我们更改内联函数的代码,则需要使用它重新编译所有代码,以确保将其更新(出于相同的原因,我避免使用函数参数的默认值)
- 当在标头中使用时,它会使标头文件变大,从而将用用户不关心的代码来稀释有趣的信息(例如类方法的列表)(这就是我在a内声明内联函数的原因)类,但将在类主体之后的标头中定义它,而永远不要在类主体中定义它)。
内联魔术
- 编译器可能会内联,也可能不会内联我们标记为内联的函数;它也可能决定在编译或者链接时将未标记为内联的内联函数。
- 内联的工作方式类似于由编译器控制的复制/粘贴操作,这与预处理器宏有很大不同:宏将被强制内联,将污染所有名称空间和代码,不容易调试,甚至可以完成如果编译器认为它效率低下。
- 在类本身内定义的类的每个方法都被视为"内联"(即使编译器仍可以决定不对其进行内联)
- 虚拟方法不应该是无懈可击的。有时候,当编译器可以肯定地知道对象的类型时(即在同一函数体内声明并构造了对象),即使是虚函数也将被内联,因为编译器确切地知道了对象的类型。
- 模板方法/函数并不总是内联的(它们在标题中的存在不会使其自动内联)。
- "内联"之后的下一步是模板元编程。 IE。通过在编译时"内联"代码,有时,编译器可以推断出函数的最终结果...因此,有时可以将复杂的算法简化为一种" return 42;"语句。对我来说,这是极端的内联。它在现实生活中很少发生,它使编译时间更长,不会膨胀代码,并使代码更快。但是像圣杯一样,不要尝试将其应用到任何地方,因为大多数处理都无法通过这种方式解决...尽管如此,这仍然很酷... :-p
在优化过程中,即使我们未标记函数,许多编译器也会内联函数。如果我们不知道编译器不了解的内容,通常只需要将函数标记为内联即可,因为它通常可以自行做出正确的决定。
来自此处另一次讨论的结论:
内联函数有什么缺点吗?
显然,使用内联函数没有错。
但是值得注意以下几点!
- 内联的过度使用实际上会使程序变慢。根据函数的大小,内联它可能导致代码大小增加或者减少。内联一个非常小的访问器函数通常会减小代码大小,而内联一个非常大的函数则可以大大增加代码大小。在现代处理器上,由于更好地利用了指令高速缓存,较小的代码通常运行速度更快。 -Google准则
- 内联函数的速度优势往往会随着函数大小的增加而减少。在某些时候,与函数主体的执行相比,函数调用的开销变小,并且失去了好处-源
- 对于不返回任何值的函数;如果存在循环,开关或者转到语句。
- 如果函数是递归的。 -来源
- 仅当我们指定optimize选项时,关键字__inline才会使函数内联。如果指定了optimize,是否接受__inline取决于内联优化器选项的设置。默认情况下,只要运行优化器,内联选项就会生效。如果我们指定optimize,那么如果我们想忽略__inline关键字,还必须指定noinline选项。 -来源
一般来说,如今,任何现代编译器都担心内联任何东西,这几乎是在浪费时间。实际上,编译器应该通过自己对代码的分析以及传递给编译器的优化标志的规范来为我们优化所有这些注意事项。如果我们关心速度,请告诉编译器优化速度。如果我们关心空间,请告诉编译器优化空间。提到的另一个答案是,如果一个合理的编译器会自动内联。
同样,正如其他人所述,使用内联并不保证任何内联。如果要保证它,则必须定义一个宏而不是一个内联函数。
何时内联和/或者定义一个宏以强制包含?仅当已知的关键代码段的速度得到了证明的和必要的事实证明的提高时,已知该代码的关键部分会影响应用程序的整体性能。
我想补充一点,在构建共享库时,内联函数至关重要。无需内联标记函数,它将以二进制形式导出到库中。如果已导出,它将也出现在符号表中。另一方面,内联函数既不导出到库二进制文件也不导出到符号表。
当打算在运行时加载库时,这可能很关键。它还可能会访问二进制兼容感知库。在这种情况下,请勿使用内联。