我能否有选择地(强制)内联一个函数?

37
在书籍Clean Code(以及我遇到和阅读的其他一些书籍)中,建议保持函数小并在它们变得庞大时将它们分解。它还建议函数应该只做一件事情。
Optimizing software in C++中,Agner Fog指出他不喜欢仅仅因为一个函数跨越了一定数量的行数就将其分解的规则。他认为这会导致不必要的跳转,从而降低性能。
首先,我明白如果我正在处理的代码不在紧密循环中,并且函数很重,那么调用它们所需的时间将被函数内部代码执行所占据的时间所淹没。但是让我们假设我正在使用大多数时间都由其他对象/函数使用并执行相对琐碎任务的函数。这些函数遵循上面提到的建议(即只执行单个功能并且小/易于理解)。然后,我开始编写一个性能关键函数,它在紧密循环中利用这些其他函数,并且本质上是一帧函数。最后,假设将它们内联对性能关键函数有益处,但对任何其他函数没有任何好处(是的,我已经进行了分析,尽管需要大量复制和粘贴,但我想避免这种情况)。

立即可以说将函数标记为inline,让编译器选择。但如果我不想让所有这些函数都在一个.inl文件中或者在头文件中公开呢?在我的当前情况下,性能关键的函数和它使用的其他函数都在同一个源文件中。

总之,我是否可以有选择地(强制)内联一个函数(多个函数),以便最终代码的行为就像是一个大函数而不是对其他函数的多次调用。


除非您使用预处理器,否则任何指示函数应内联的操作都可能会被编译器忽略。 - IronMensan
3
gcc提供了-Winline选项,用于警告那些被标记为“inline”但实际上没有内联的函数。然而,这只是一个开始,而不是解决问题的方法。 - pmr
由于所有函数都在正确的源文件中,并且没有在其他地方使用,我建议只需将它们标记为static inline并让编译器处理即可。如果它认为通过不内联它们会产生更好的代码,谁知道,也许它是对的。如果您指定编译器,人们可以建议任何其具有的选项来强制内联,但由于您似乎想要一个可移植解决方案,所以"让编译器优化,它比您更了解平台,因为您几乎不知道该平台"。 - Steve Jessop
我认为应该有一种方法来解决这个问题。我创建了一个STL算法的函数版本,但GCC和clang编译器不会将该函数内联,并且需要的时间比STL多10倍,当我将函数粘贴到内部时,只需要0.9倍的时间。所以有时候需要强制内联。 - Moises Rojo
9个回答

21

没有任何东西能够阻止你在.cpp文件里的静态函数中使用内联(inline)

一些编译器提供了强制内联函数的选项,例如GCC的属性(attribute)((always_inline))和大量微调内联优化的选项(参见-minline-*参数)。

我的建议是,在适当的地方使用内联(inline)或者更好的静态内联(static inline),并让编译器决定。它们通常做得很好。


这是一个不错的建议。在我的特定情况下它不起作用,但肯定值得记住(+1)。 - Samaursa
2
在一些主要是嵌入式项目中,代码的逻辑分离但“物理”连续性的需求经常出现。例如考虑中断处理程序例程,在其中必须执行许多强制操作,既在开头也在结尾。只要保证中断例程不会被额外的堆栈操作和调用指令污染(即函数真正被内联),这些操作可以完美地实现为静态内联函数。 - Géza Török

17

您不能强制进行内联优化。此外,在现代CPU上,与所执行的工作成本相比,函数调用非常便宜。如果您的函数足够大需要拆分,那么进行函数调用所需的额外时间基本上可以忽略不计。

如果无法实现内联优化,您可以尝试使用宏。


2
我改变了我的编程风格,以符合各种书籍作者的建议(他们比我更懂),通过编写函数来实现它们名称所示的功能,不多也不少,并保持函数垂直长度较小。这导致了许多较小的函数,对我最近性能敏感的代码产生了影响。在阅读了Agner Fog的书之后,我开始思考另一个方面,想知道是否应该在两种相反的建议之间取得平衡(选择性内联可能是理想的解决方案 - 宏可能是其中之一)。 - Samaursa
4
现在你可以使用__forceinline(https://msdn.microsoft.com/zh-cn/bw1hbe6y)关键字。 - gj13
2
forceinline指示编译器尽最大努力内联函数,而不进行任何成本/效益分析。https://learn.microsoft.com/en-us/cpp/cpp/inline-functions-cpp#inline-__inline-and-__forceinline - Sachin Joseph
这已经过时了,请查看下面的其他答案。 - Epic Speedy
将__attribute__((always_inline))添加到我的内联函数中使我的库快了36%,因此它是有益的。 - Jakob Kenda

12

不,inline只是向编译器发出的一个推荐指令,并不能强制执行。此外,如果您正在使用MSVC ++,请注意,__forceinline也是错误的命名方式;它只是比 inline 更强烈的建议。


4
我了解。编译器无论如何都无法内联某些函数,但是当使用__forceinline时,编译器将不考虑自身的分析,如果可以内联则该函数将被内联。 - Samaursa
@Samaursa:是的,但整个重点在于您永远不能假设该函数将使用任何变体的“inline”关键字进行内联(根据您的问题)。最多,您可能能够增加它被内联的机会。 - Jacob
2
@Jacob:你不能假设它,但是你可以通过启用/Wall的所有警告并注意哪些函数没有被内联来验证它。 - user541686
1
@Jacob 确实,我曾经怀疑过,但你是正确的。即使使用 __forceinline 关键字,也无法强制编译器内联特定函数。 (http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx) - bobobobo
这里有大量信息:https://learn.microsoft.com/en-us/cpp/cpp/inline-functions-cpp#inline-__inline-and-__forceinline - Sachin Joseph

10

这与 C++ 一样关乎于好老的纯 C 语言。最近我在思考这个问题,因为在嵌入式世界中,需要仔细管理速度和空间,这非常重要(与桌面/服务器开发中“别担心,你的编译器很聪明,内存很便宜”的情况相比)。

我尚未经过验证的一个可能的解决方案是基本上使用两个名称来区分不同的变体,例如

inline int _max(int a, int b) {
    return a > b ? a : b;
}

然后

int max(int a, int b) {
    return _max(a, b);
}
这将使您能够有选择性地调用_max()或max(),同时仍然只定义一次算法。

2

如果您有一个已知的热点函数,并且希望编译器比通常更积极地进行内联,则gcc / clang提供的flatten属性可能是值得考虑的东西。与inline关键字和属性相反,它适用于标记函数中调用的函数的内联决策。

__attribute__((flatten)) void hot_code() {
    // functions called here will be inlined if possible
}

请查阅官方文档了解更多关于通用函数属性和展开的信息。链接如下:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.htmlhttps://clang.llvm.org/docs/AttributeReference.html#flatten

2
内联 - 例如,如果存在一个频繁调用相对较小的函数B的函数A,那么基于性能指导的优化将会在函数A中将函数B内联。 VS基于性能指导的优化 您可以在Visual Studio中使用自动化的Profile Guided Optimization for Visual C++插件来简化和优化流程,也可以手动在Visual Studio或命令行中执行优化步骤。我们推荐使用插件,因为它更易于使用。有关如何获取插件并使用它来优化应用程序的信息,请参阅Profile Guided Optimization Plug-In

1

很惊讶这个还没有被提到,但是现在你可以告诉编译器(我相信它可能只适用于GCC/G++),强制内联函数并忽略与之相关的一些限制。

你可以通过__attribute__((always_inline))实现。

以下是使用示例:

inline __attribute__((always_inline)) int pleaseInlineThis() {
   return 5;
}

通常情况下,你应该避免强制使用内联,因为编译器比你更了解最佳实践;但是在操作系统/微控制器开发等多种情况下,需要内联调用,否则会破坏功能。
C++编译器通常对没有一些技巧的受控环境不太友好。

1

编译器实际上非常擅长生成优化代码。

我建议只需将您的代码组织成逻辑分组(如果需要增强可读性,可以使用额外的函数进行标记),并在适当时将它们标记为内联,然后让编译器决定要生成什么样的最佳代码。


1
在某些情况下不适用:有时,inline被用作宏的替代品,因为它们易于调试、更易读,并且可以放入命名空间中。有时,堆栈帧分配可能会积累成宝贵的微秒延迟,并且编译器可能不知道执行频率而没有上下文(如果您内联一个 sin() 函数并且编译器由于某种原因没有内联它怎么办?)。 - Kotauskas

0
如大家所说,你应该避免这样做,因为编译器通常会做出更好的决策。有几个优化方法可以提高性能。如果需要,这些优化方法将内联函数:
  • LTO:链接时优化或过程间优化
  • 基于运行时分析的优化:基于运行时分析的优化
  • BOLT:二进制优化和布局工具
  • Polly:高级循环和数据本地性优化器

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接