【问题标题】:c++ heuristic for estimating function inlining benefitsc++ 启发式估计函数内联的好处
【发布时间】:2011-10-29 18:36:27
【问题描述】:

在 C++ 中,什么是估计内联函数的计算时间优势的良好启发式方法,特别是当函数被非常频繁地调用并且占程序执行时间的 >= 10% 时(例如,蛮力或随机优化过程)。尽管内联最终可能超出我的控制范围,但我仍然很好奇。

【问题讨论】:

  • 如果它被递归调用,那么它不能被完全内联(除非递归是静态有界的,但这很难检测到)。
  • 您知道可以使用__inline__ (GCC) 或__forceinline (VC) 对内联进行“终极”控制吗?
  • @larsmans 错了。阅读尾声。但即使是非尾递归函数有时也可以自动重构和内联。
  • @Konrad: 如果执行了 tail-recursion 优化,那么递归已经隐式内联了,所以尝试inline 外部调用(除非成本与递归无关,而是每个递归级别的成本问题,或者递归级别最小)
  • @Constantinius:Dong so 通常会适得其反。编译器在决定何时内联方面比人类好得多。强制编译器通常会导致次优代码。

标签: c++ optimization inlining


【解决方案1】:

所有内联为您节省的是函数的进入/退出成本,因此只有在函数几乎什么都不做的情况下才值得考虑。 当然,如果函数本身包含函数调用,则可能不值得考虑。

即使函数做的很少,它也必须被调用得如此之多,以至于它在相当大比例的时间内拥有程序计数器,然后才能注意到函数的任何加速。

【讨论】:

  • 节省取决于优化管道内联的执行位置。如果做得足够早,可能会在生成的代码上进行持续传播。
【解决方案2】:

我们拥有 20 多年编写计算密集型 C++ 的经验,内联不是灵丹妙药。您确实需要分析您的代码以查看内联是否会提高性能。对我们来说,除了低级别的 2D 和 3D 点和矢量操作外,内联是浪费时间。与尝试对时钟滴答进行微观管理相比,制定更好的算法要好得多。

【讨论】:

    【解决方案3】:

    没有普遍的答案。这取决于硬件、数量和 它的参数的类型,以及在函数中做了什么。以及多久 它被称为,在哪里。例如,在 Sparc 上,参数(以及 返回值)在寄存器中传递,每个函数得到 16 个新的 寄存器:如果函数足够复杂,那些新的寄存器可能 避免在函数被内联时发生的溢出,并且 非内联版本最终可能比内联版本更快。在英特尔上, 这是寄存器差,并在寄存器中传递参数,只是 对于同一程序中的同一功能,可能相反。更多的 通常,内联可能会增加程序大小,减少局部性。要么 对于非常简单的功能,它可能会减小程序大小;但这又是 取决于架构。唯一可能知道的方法是尝试 两者,测量时间。即使那样你也只会知道 特定的程序,在特定的硬件上。

    【讨论】:

    • 你的意思是英特尔的“在堆栈上传递参数”(好吧,“英特尔”是模棱两可的,对于安腾你是对的[虽然那些不是注册穷人],但我假设你的意思是 x86 )
    • @Voo 两者都是:“在堆栈上传递参数”,而英特尔指的是 x86。
    • +1,x64 中的一些调用约定倾向于在寄存器中传递更多参数,但推理是合理的:这取决于
    【解决方案4】:

    如果您的瓶颈在递归函数中,并且假设递归级别不是最小的(即平均递归不仅仅是几个级别),那么您最好在函数中使用算法而不是使用 内联。

    如果可能,尝试将递归转换为循环或 tail-recursion(编译器可以将其隐式转换为循环),或者尝试确定函数中的成本正在花费。尽量减少内部操作的影响(也许您正在动态分配可能具有自动存储持续时间的内存,或者您可以考虑在包装器中在函数外部执行的常见操作并作为额外参数传入。 ..)

    *在注释后编辑 recursion 不是有意的,而是迭代*

    如果编译器可以访问函数的定义,它会在大多数情况下为您做出正确的决定。如果它无权访问定义,只需移动代码以便它确实看到它。也许将函数设为 static 函数以提供额外的提示,表明它不会在其他任何地方使用,或者甚至将其标记为 inline(知道这不会强制内联),但避免使用将 强制内联,因为编译器可能比任何无需查看代码即可生成的简单启发式方法做得更好。

    【讨论】:

    • 酷。有问题的函数是在main中调用的成员函数,main有#include "class.h",而函数在class.cpp中定义。编译器是否有可能访问该定义?
    • @Matt Munson:一般情况下没有。一些链接器可以执行链接时间内联作为整个程序优化的一部分,并且可能能够将其取消,但通常它不可用。另请注意,根据调用的成本(通过内联可以避免)相对于函数中代码执行的实际成本,它可能根本不会提供任何优势。
    【解决方案5】:

    这里的行为在某种程度上取决于编译器。使用递归函数,显然内联行为理论上可以是无限的。 'inline' 关键字只是给编译器的一个提示,如果它不能用它做任何事情,它可以选择它忽略。一些编译器会将递归函数内联到一定深度。

    至于“这将加快多少速度” - 不幸的是,我们无法为这个问题提供任何答案,因为“这取决于” - 函数做了多少工作与函数调用机制的开销本身。你为什么不设置一个测试看看?

    【讨论】:

      【解决方案6】:

      在某些架构上,函数调用和返回每条指令只需一条指令(尽管它们通常不是类似 RISC 的单周期指令。)通常,您可以将其与主体表示的周期数进行比较的功能。一个简单的属性访问可能只有一条指令,因此将其放入非内联函数将使执行它的指令数量增加三倍——显然是内联的一个很好的候选者。另一方面,一个格式化字符串以供打印的函数可能代表数百条指令,所以再多两条根本不会有任何区别。

      【讨论】:

      • 调用需要这么少指令的可能性有多大,有没有办法猜测特定函数需要多少指令?
      • 您必须 (1) 了解足够的汇编语言才能大致了解如何编译函数,以及 (2) 至少大致了解编译器实现的 C++ 对象模型。这是一个很长的说不,你真的猜不到;你只需要知道
      • 任何特定的内联函数是否更快并不是全部。对函数的调用次数必须足够大,才能保证内联。内联在头文件混乱或在找到代码的第三个位置方面有其自身的成本。此外,如果函数是内联的,则不能简单地在一个位置插入断点以便在访问函数时停止。
      猜你喜欢
      • 2011-01-08
      • 1970-01-01
      • 2011-04-26
      • 2021-10-27
      • 2011-06-13
      • 2016-02-12
      • 2017-01-05
      • 2010-11-04
      • 2011-08-23
      相关资源
      最近更新 更多