我什么时候应该使用__forceinline而不是内联?

时间:2008-09-30 05:58:54

标签: c++ visual-studio inline-functions

Visual Studio包含对__forceinline的支持。 Microsoft Visual Studio 2005文档声明:

  

__forceinline关键字覆盖   成本/效益分析和依赖   关于程序员的判断   代替。

这提出了一个问题:编译器的成本/收益分析何时出错?而且,我怎么知道这是错的?

在什么情况下假设我在这个问题上比我的编译器更清楚?

12 个答案:

答案 0 :(得分:43)

只有当您的分析数据告诉您时,您才会比编译器更清楚。

答案 1 :(得分:34)

我使用它的一个地方是许可证验证。

防止轻松破解的一个重要因素是验证在多个地方而不是一个地方获得许可,并且您不希望这些地方成为同一个函数调用。


*)请不要在讨论中将所有内容都破解 - 我知道。而且,仅这一点并没有多大帮助。

答案 2 :(得分:18)

编译器正在根据静态代码分析做出决策,而如果你描述为唐说,那么你正在进行一个可以进一步深入的动态分析。对特定代码段的调用次数通常很大程度上取决于使用它的上下文,例如,数据。分析一组典型的用例将执行此操作。就个人而言,我通过启用自动回归测试的分析来收集这些信息。除了强制内联之外,我还展开了循环,并根据这些数据进行了其他手动优化,效果很好。之后再次进行分析也是必要的,因为有时您的最佳努力实际上会导致性能下降。再一次,自动化使这不那么痛苦。

通常情况下,根据我的经验,调整算法比直接优化代码提供了更好的结果。

答案 3 :(得分:8)

我已经为有限的资源设备开发了9年左右的软件而且唯一的时间我曾经看到需要使用__forceinline的时候是一个紧凑的环路,相机驱动程序需要将像素数据从捕获缓冲区复制到设备屏幕。在那里我们可以清楚地看到特定函数调用的成本确实影响了叠加绘制性能。

答案 4 :(得分:6)

唯一可以确定的方法是使用和不使用来衡量性能。除非您编写高性能关键代码,否则通常不需要这样做。

答案 5 :(得分:3)

当用于以下函数时,内联指令完全没用:

递归的, 长, 由循环组成,

如果您想使用__forceinline强制执行此决定

答案 6 :(得分:3)

实际上,即使使用__forceinline关键字也是如此。 Visual C ++有时会选择不内联代码。 (来源:结果汇编源代码。)

始终查看速度非常重要的结果汇编代码(例如需要在每个帧上运行紧密的内部循环)。

有时使用#define而不是inline就可以了。 (当然你通过使用#define来进行大量检查,所以只在它真正重要的时候和地点使用它)。

答案 7 :(得分:2)

用于SIMD代码。

SIMD代码通常使用常数/幻数。在常规函数中,每个const __m128 c = _mm_setr_ps(1,2,3,4);都成为一个内存引用。

使用__forceinline,编译器可以加载一次并重用该值,除非您的代码耗尽了寄存器(通常为16)。

CPU缓存很棒,但是寄存器仍然更快。

P.S。仅靠__forceinline就能使性能提高12%。

答案 8 :(得分:1)

在某些情况下,编译器无法明确地确定内联函数是否合适或有益。内联可能涉及编译器不愿意做出的权衡,但你是(例如代码膨胀)。

总的来说,现代编译器实际上非常擅长做出这个决定。

答案 9 :(得分:1)

如果您知道要在一个地方多次调用该函数以进行复杂计算,那么最好使用__forceinline。例如,动画的矩阵乘法可能需要被调用很多次,以至于探查器会开始注意到对函数的调用。正如其他人所说,编译器无法真正了解这一点,特别是在编译时未知代码执行的动态情况下。

答案 10 :(得分:1)

实际上,加载了它。

例如

 BOOST_CONTAINER_FORCEINLINE flat_tree&  operator=(BOOST_RV_REF(flat_tree) x)
    BOOST_NOEXCEPT_IF( (allocator_traits_type::propagate_on_container_move_assignment::value ||
                        allocator_traits_type::is_always_equal::value) &&
                         boost::container::container_detail::is_nothrow_move_assignable<Compare>::value)
 {  m_data = boost::move(x.m_data); return *this;  }

 BOOST_CONTAINER_FORCEINLINE const value_compare &priv_value_comp() const
 { return static_cast<const value_compare &>(this->m_data); }

 BOOST_CONTAINER_FORCEINLINE value_compare &priv_value_comp()
 { return static_cast<value_compare &>(this->m_data); }

 BOOST_CONTAINER_FORCEINLINE const key_compare &priv_key_comp() const
 { return this->priv_value_comp().get_comp(); }

 BOOST_CONTAINER_FORCEINLINE key_compare &priv_key_comp()
 { return this->priv_value_comp().get_comp(); }

 public:
 // accessors:
 BOOST_CONTAINER_FORCEINLINE Compare key_comp() const
 { return this->m_data.get_comp(); }

 BOOST_CONTAINER_FORCEINLINE value_compare value_comp() const
 { return this->m_data; }

 BOOST_CONTAINER_FORCEINLINE allocator_type get_allocator() const
 { return this->m_data.m_vect.get_allocator(); }

 BOOST_CONTAINER_FORCEINLINE const stored_allocator_type &get_stored_allocator() const
 {  return this->m_data.m_vect.get_stored_allocator(); }

 BOOST_CONTAINER_FORCEINLINE stored_allocator_type &get_stored_allocator()
 {  return this->m_data.m_vect.get_stored_allocator(); }

 BOOST_CONTAINER_FORCEINLINE iterator begin()
 { return this->m_data.m_vect.begin(); }

 BOOST_CONTAINER_FORCEINLINE const_iterator begin() const
 { return this->cbegin(); }

 BOOST_CONTAINER_FORCEINLINE const_iterator cbegin() const
 { return this->m_data.m_vect.begin(); }

答案 11 :(得分:1)

w noinline的情况

我想提出一个不寻常的建议,实际上是为MSVC中的__noinline或在GCC和ICC中的noinline属性/编译指示提供担保,以替代尝试先于__forceinline以及盯着探查器热点时的等效内容。 YMMV,但我告诉编译器不要内联永远总是比总是内联 多得多(可衡量的改进)。在分析这些变化时,它的侵入性也往往要低得多,并且会产生更多可预测和可理解的热点。

尽管通过告诉编译器内联什么不是来尝试提高性能可能看起来很违反直觉并且有些落后,但我会根据我的经验声称它要更多与优化编译器的工作方式和谐一致,并且对代码生成的侵害远不那么严重。一个容易忘记的细节是:

内联callee经常会导致callercaller的调用者停止内联。

这就是迫使强制对代码生成进行相当侵入性的更改的原因,该更改可能在配置文件会话中产生混乱的结果。我什至遇到过这样的情况:强制内联函数在多个地方重复使用,以非常混乱的方式彻底改组了位置上具有最高自采样的前十个热点。有时到了某种程度,我觉得自己正在与优化器进行斗争,使此处的事情变得更快,而在同等常见的用例中,尤其是在诸如字节码解释之类的优化器棘手的情况下,它只是交换了其他地方的减速。我发现noinline方法可以轻松成功地根除热点,而无需在其他地方交换一个。

如果我们能够以较低的侵入性内联函数,则可能 可以在呼叫站点内联而不是确定是否 每个对函数的调用都应内联。不幸的是,我 除了ICC之外,没有发现许多编译器支持这种功能。它 如果我们对热点做出反应,对我来说更有意义 通过在呼叫站点进行内联而不是每次拨打一个电话 内联特定功能。缺乏这种广泛的支持 在大多数编译器中,我获得了更成功的结果 noinline

使用noinline

进行优化

因此,使用noinline进行优化的想法仍怀着相同的目标:帮助优化器内联我们最关键的功能。不同之处在于,我们不是通过强制内联来试图告诉编译器它们是什么,而是相反地通过强制阻止内联函数来告诉编译器哪些函数绝对不是关键执行路径的一部分。我们将重点放在确定罕见情况下的非关键路径上,同时让编译器仍然可以自由选择要在关键路径中内联的内容。

假设您有一个循环执行一百万次迭代,并且有一个名为baz的函数在该循环中很少被调用,平均而言,即使响应非常不寻常的用户输入,平均每千次迭代也会调用一次尽管它只有5行代码,没有复杂的表达式。您已经分析了此代码,分析器在反汇编中显示,调用函数foo然后调用baz的示例数量最多,并且在调用指令周围分布了许多示例。自然的诱惑可能是强制内联foo。我建议改为尝试将baz标记为noinline并计时结果。这样,我设法使某些关键循环的执行速度提高了3倍。

分析生成的程序集后,由于不再将foo内联到其主体中,因此baz函数现在已被内联。

enter image description here

在这种情况下,我经常发现用baz标记类似的noinline比强制内联foo可以产生更大的改进。我不是计算机架构向导,无法确切地理解为什么,但在这种情况下,我们只看了分析器中样本的分解和分布,强制内联foo的结果是编译器仍在内联很少执行的代码baz位于foo之上,通过仍然内联稀有函数调用,使foo变得更加肿。通过简单地用baz标记noinline,我们允许foo在以前不被内联的情况下实际上也内联baz。我仍然不能完全理解为什么内联baz导致的额外代码会降低整体功能的速度;以我的经验,跳转指令到更远的代码路径似乎比跳转更耗时,但是我不知道为什么(也许与跳转指令在更大的操作数上花费更多时间有关,或者使用指令缓存)。我可以肯定地说的是,在这种情况下支持noinline可以提供出色的性能来强制内联,并且在随后的性能分析会话中也没有这种破坏性的结果。

因此,无论如何,我建议尝试尝试noinline并在强制内联之前先进行尝试。

人与优化器

在哪种情况下,假设我比编译器更了解 这个问题?

我不要大胆假设。至少我做不到这一点。如果有的话,多年来,我已经了解到一个愚蠢的事实,那就是,一旦我检查并评估了探查器所尝试的操作,我的假设通常就会出错。我已经走上了舞台(过去几十年让我的探查器成为我的最好的朋友),以避免在黑暗中完全盲目刺伤,只是要面对谦卑的失败并恢复我的改变,但是在最好的情况下,我仍然在大多数,有根据的猜测。尽管如此,我总是比我的编译器更了解,并且希望我们大多数程序员比我们的编译器更了解这一点,我们应该如何设计产品以及如何最有可能被客户使用。至少这使我们在理解编译器不具备的普通用例和稀有用例代码分支方面有了一些优势(至少在没有PGO的情况下,而我从未从PGO中获得最佳结果)。编译器不具备这种类型的运行时信息,也不具有常见用户输入的预见性。正是当我结合这些用户端知识以及手头的探查器时,我发现在这里和那里的优化器上进行了一些最大的改进,例如在进行内联或在我的情况下更常见的内容是什么。永远不要内联。