何时使用内联功能何时不使用?

时间:2009-12-19 08:00:26

标签: c++ c inline

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

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

14 个答案:

答案 0 :(得分:189)

避免功能调用的成本只是故事的一半。

做的:

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

     

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

EDIT2:ISO-IEC 14882-1998,7.1.2函数说明符

  

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

答案 1 :(得分:52)

inline与优化几乎没有关系。 inline是一个指令,如果给定定义的函数在程序中多次出现,并且承诺定义将在每个使用它的转换中发生,并且在它出现的任何地方都会出现错误,那么编译器不会产生错误。完全相同的定义。

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

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

答案 2 :(得分:10)

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

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

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

答案 3 :(得分:5)

最好的方法是分析您的程序并标记被调用很多次的小函数,并烧掉inline的CPU周期。这里的关键字是“小” - 一旦函数调用开销与函数中花费的时间相比可以忽略不计,内联它们就毫无意义。

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

答案 4 :(得分:4)

过早优化是万恶之源!

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

另一方面,大多数现代编译器都有相当不错的优化算法,并且内联您应该为您内联的内容。

重新尝试 - 编写内联单行函数,稍后再担心其他函数。

答案 5 :(得分:2)

内联函数可能通过消除将参数推入堆栈的需要来提高代码性能。 如果所涉及的函数位于代码的关键部分,则应在项目的优化部分中进行内联而非内联决策,

您可以在c++ faq

中详细了解内联

答案 6 :(得分:1)

我经常使用内联函数作为优化,但使代码更具可读性。有时代码本身比评论,描述性名称等更短,更容易理解。例如:

void IncreaseCount() { freeInstancesCnt++; }

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

答案 7 :(得分:0)

我通常遵循一个拇指规则,我用3-4个简单的语句作为内联函数。但值得记住的是,这只是编译器的一个提示。内联或非内联的最终调用仅由编译器执行。如果不止这些语句,我将不会像使用愚蠢的编译器一样声明内联,这可能会导致代码膨胀。

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

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

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

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

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

只有当函数代码很小时才应该使用内联函数限定符。如果函数较大,你应该更喜欢正常函数,因为保存内存空间值得相对较小的执行牺牲速度。

答案 12 :(得分:0)

当您认为您的代码足够小以用作内联时,请记住内联函数将您的代码复制并粘贴到被调用的函数中,这样可能足以增加执行时间,但同时也增加了内存消耗。 当您使用循环/静态变量/递归/开关/转到/虚拟功能时,不能使用内联功能。 虚拟方式要等到运行时和内联方式才能在编译期间进行,这样它们才能同时使用。

答案 13 :(得分:-1)

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

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

编译器足够聪明,短函数总是内联。除非程序员说要做到这一点,否则永远不要长时间使用内联功能。

  

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

实际上inline是编译器的订单,它没有选择,在inline关键字后使所有代码内联。因此,您永远不能使用inline关键字,编译器将设计最短的代码。

那么何时使用inline

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

例如我有这个功能:

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

无论这个函数有多大,我都想把它作为内联函数,因为它会让我的软件更难破解。