【问题标题】:Questions about inline performance关于内联性能的问题
【发布时间】:2015-09-15 11:58:43
【问题描述】:

我有一些关于在 C 和 C++ 中使用内联函数的问题。我被告知要在我经常使用的小功能上使用它,但我想确切地了解它是如何工作的。这里只是一个示例的 sn-p。

static inline point3D createPoint3D(float x, float y, float z){
   point3D newPosition;
   newPosition.x = x;
   newPosition.y = y;
   newPosition.z = z;
   return newPosition;
}
  1. 它究竟做了什么,为什么它可以帮助代码运行得更快?这是 90 年代过时的优化吗?

  2. 为什么我只应该在小功能上使用它?如果我为大型功能这样做会不会很糟糕?

  3. 在大量函数上使用它是不是很糟糕?

【问题讨论】:

  • 相关注意事项(如果您从未使用过 inline 很容易忘记):要使 inline 跨编译单元边界工作,您必须将函数的 body 放入 inline一个头文件,而不仅仅是原型,否则编译器在编译其他编译单元时将没有内联代码(另请参阅parashift.com/c++-faq-lite/inline-functions.html#faq-9.6)。
  • 你在这里也有一些关于内联的很好的答案:stackoverflow.com/questions/3647053/…
  • 请注意,在 C 中使用 inline 与在 C++ 中使用 inline 完全不同。后者更直接。 See here 用于 C 讨论。
  • @M.M 谢谢,这是我五年前问的一个问题,现在我对这个话题有了更多的了解。

标签: c++ c optimization inline


【解决方案1】:
  1. 这更像是 70 年代或(最多)80 年代的过时优化。几乎任何有能力的编译器都可以选择函数进行内联扩展,而无需您提供任何帮助,而无需启动优化。

  2. 它应该做的是消除调用函数的开销。这对于诸如几乎什么都不做的微小功能之类的事情最为重要。碰巧的是,这些都非常普遍,即使是在 C++ 中获得一半不错的性能,几乎都需要编译器或多或少地自动扩展内联函数。

  3. 使用它通常毫无意义。

  4. 通常不会——如上所述,当内联函数有好处时,编译器通常可以自动这样做。

需要注意两点:1) 大多数编译器可以/将会在没有 inline 关键字的情况下生成内联函数,以及 2) 大多数编译器可以/将忽略 inline 关键字,如果他们认为该函数不适合内联扩展(尽管,只是 FWIW,如果你真的确定你比编译器更了解,微软有一个 __forceinline 来克服后者)。

【讨论】:

  • 在我的例子中,如果编译器没有看到函数的定义,即使是 O3 优化级别,它也不会内联简单的 getter 函数。换句话说,只有当定义在头文件本身中时,第一点才成立。
【解决方案2】:

请在 C++ 常见问题解答here 中查看此详细信息。引用这个内联函数..

当编译器内联扩展函数调用时,函数的代码 被插入到调用者的代码中 流(概念上类似于什么 发生在#define 宏中)。这个 可以,取决于无数其他 事情,提高性能,因为 优化器可以在程序上 整合被调用的代码——优化 被调用代码进入调用者。

第 9.3 节

内联函数可能会使其更快: 如上图,程序集成 可能会删除一堆不必要的 说明,这可能会使事情 跑得更快。

内联函数可能会使其变慢: 过多的内联可能会导致代码 膨胀,这可能会导致“颠簸” 按需分页虚拟内存 系统。换句话说,如果 可执行文件太大,系统 可能大部分时间都在外出 到磁盘以获取下一个块 代码。

内联函数可能会使它变大: 这是代码膨胀的概念,因为 如上所述。例如,如果一个 系统每个有 100 个内联函数 其中扩展为 100 字节 可执行代码并在 100 中调用 地方,增加了1MB。是 那1MB会引起问题吗?谁 知道,但有可能 最后 1MB 可能会导致系统 “thrash”,这可能会减慢速度 下来。

内联函数可能会成功 较小:编译器经常生成 更多代码推送/弹出 寄存器/参数比它会 内联扩展函数的主体。 这发生在非常小的 函数,它也发生在 当优化器是大函数时 能够删除大量冗余代码 通过程序整合——即 是,当优化器能够使 大功能小。

内联函数可能会导致 thrashing:内联可能会增加 二进制可执行文件的大小,以及 这可能会导致颠簸。

内联函数可能会阻止 thrashing:工作集大小 (需要进入的页数 内存一次)可能会下降,即使 可执行文件大小增加。当 f() 调用g(),代码经常在两个 不同的页面;当编译器 在程序上集成代码 g() 改成 f(),代码经常放在 同一页。

内联函数可能会增加 缓存未命中数:内联可能 导致内部循环跨越 多行内存缓存, 这可能会导致颠簸 内存缓存。

内联函数可能会减少 缓存未命中数:内联 通常会提高参考的局部性 在二进制代码中,这可能 减少缓存行数 需要存储内部代码 环形。这最终可能导致 CPU 密集型应用程序运行得更快。

内联函数可能无关紧要 速度:大多数系统都没有 受 CPU 限制。大多数系统都是 I/O-bound, 数据库绑定或网络绑定, 意味着系统的瓶颈 整体性能是文件 系统、数据库或网络。 除非您的“CPU 仪表”固定在 100%,内联函数可能不会 让您的系统更快。 (即使在 受 CPU 限制的系统,内联将有所帮助 仅在瓶颈内使用时 本身,瓶颈是 通常只有一小部分 代码。)

没有简单的答案:你有 玩它,看看什么是最好的。 不要满足于简单的答案 比如,“永远不要使用内联函数”或 “始终使用内联函数”或“使用 内联函数当且仅当 函数小于N行 代码。”这些千篇一律的规则 可能很容易写下来,但它们 将产生次优结果。

【讨论】:

  • 所以如果我不关心文件大小,我可以在任何事情上随意使用它?
【解决方案3】:

别担心。 在你测量之前都是一样的。一旦你测量了,你不会注意到没有inline的使用ot编译的版本之间有很大的差异。

1) inline 是对编译器的建议,将函数直接“内联”在代码流中,而不是“调用”它。这绕过了设置堆栈和执行调用函数所需的其他琐事的需要

非内联内联 …… 代码代码 从 fx 调用 fx -\ 代码 代码 |来自 fx 的代码 调用 fx --|来自 fx 的代码 ... |代码 |来自 fx 的代码 代码

2) 随时随地使用它。编译器很可能会忽略您的建议

3) 同 2)

4) 测量。实验和比较

【讨论】:

    【解决方案4】:

    inline 关键字表示您认为该函数是一个很好的候选者,可以包含该函数以代替对该函数的调用。它最适合用于小型函数,因为每次使用它都会在使用点放置一个新的函数体副本。过度使用可能会大大增加调用代码的大小。

    它很有价值,因为如果优化器可以看到一个小函数的内部,有时它可以做得更好。通过将函数体内联,优化器获得了这个机会。它还提高了执行线程的引用局部性,从而可以提高指令缓存和流水线的性能。

    在经典 C 中,获得这种效果的唯一方法是使用宏,但宏有一个明显的缺点,即它们是纯文本替换,因此它们会导致它们的每个参数在每次出现时都被评估替换文本。如何安全地允许宏具有局部变量也是不明显的。

    在 C++ 中,允许作为该语言的常见习惯用法的小型访问器函数内联通常具有巨大的优势,以至于其主体在类定义中定义的函数被隐式标记为 inline

    一个好的优化器会自行决定何时实际使用内联函数以及何时正常调用它,因此将函数随意标记为inline 通常不会产生太大的不利影响。

    【讨论】:

      猜你喜欢
      • 2011-04-19
      • 2011-05-28
      • 1970-01-01
      • 2022-09-24
      • 1970-01-01
      • 2020-06-30
      • 1970-01-01
      • 1970-01-01
      • 2019-11-27
      相关资源
      最近更新 更多