Visual Studio 包括对 __forceinline 的支持。Microsoft Visual Studio 2005 文档指出:
__forceinline 关键字覆盖了成本/收益分析,而是依赖于程序员的判断。
这就提出了一个问题:编译器的成本/收益分析什么时候出错了?而且,我怎么知道这是错的?
在什么情况下假设我在这个问题上比我的编译器更了解?
Visual Studio 包括对 __forceinline 的支持。Microsoft Visual Studio 2005 文档指出:
__forceinline 关键字覆盖了成本/收益分析,而是依赖于程序员的判断。
这就提出了一个问题:编译器的成本/收益分析什么时候出错了?而且,我怎么知道这是错的?
在什么情况下假设我在这个问题上比我的编译器更了解?
只有当您的分析数据告诉您时,您才比编译器更清楚。
我使用它的一个地方是许可证验证。
防止容易* 破解的一个重要因素是验证在多个地方而不是仅在一个地方获得许可,并且您不希望这些地方是同一个函数调用。
*)请不要在讨论中讨论一切都可以破解 - 我知道。此外,仅此一项并没有太大帮助。
编译器根据静态代码分析做出决定,而如果您按照唐所说的那样进行分析,那么您正在执行的动态分析可能会更远。对特定代码段的调用次数通常很大程度上取决于使用它的上下文,例如数据。分析一组典型的用例就可以做到这一点。就个人而言,我通过在我的自动回归测试中启用分析来收集这些信息。除了强制内联,我还根据这些数据展开了循环,并进行了其他手动优化,效果很好。事后再次分析也是必要的,因为有时您的最大努力实际上会导致性能下降。同样,自动化使这变得不那么痛苦。
不过,根据我的经验,通常情况下,调整算法会比直接优化代码提供更好的结果。
我为资源有限的设备开发软件已有 9 年左右的时间,我唯一__forceinline
一次看到需要使用的是在一个紧密的循环中,其中相机驱动程序需要将像素数据从捕获缓冲区复制到设备屏幕。在那里我们可以清楚地看到特定函数调用的成本确实占用了叠加绘制性能。
唯一可以确定的方法是衡量有无的性能。除非您正在编写高性能的关键代码,否则这通常是不必要的。
对于 SIMD 代码。
SIMD 代码经常使用常量/幻数。在常规函数中,everyconst __m128 c = _mm_setr_ps(1,2,3,4);
成为内存引用。
使用__forceinline
,编译器可以加载一次并重用该值,除非您的代码耗尽了寄存器(通常为 16 个)。
CPU 缓存很棒,但寄存器仍然更快。
PS 一个人就获得了 12% 的性能提升__forceinline
。
当用于以下功能时,内联指令将完全没有用:
递归的,长的,由循环组成,
如果您想使用 __forceinline 强制执行此决定
实际上,即使使用 __forceinline 关键字。Visual C++ 有时会选择不内联代码。(来源:生成的汇编源代码。)
始终查看生成的汇编代码,其中速度很重要(例如需要在每一帧上运行紧密的内部循环)。
有时使用 #define 而不是 inline 可以解决问题。(当然,使用#define 会减少很多检查,因此仅在真正重要的时间和地点使用它)。
在几种情况下,编译器无法明确地确定内联函数是否合适或有益。内联可能涉及编译器不愿意做出的权衡,但您愿意(例如,代码膨胀)。
一般来说,现代编译器实际上非常擅长做出这个决定。
当您知道要在一个地方多次调用该函数以进行复杂的计算时,使用 __forceinline 是一个好主意。例如,动画的矩阵乘法可能需要调用很多次,以至于对函数的调用将开始被您的分析器注意到。正如其他人所说,编译器无法真正知道这一点,尤其是在编译时代码执行未知的动态情况下。
实际上,boost 已经加载了它。
例如
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(); }
w一个案例noinline
我想提出一个不寻常的建议,并实际上__noinline
在 MSVC 或noinline
GCC 和 ICC 中的属性/编译指示中担保,作为在查看分析器热点时首先尝试__forceinline
及其等价物的替代方案。YMMV,但是告诉编译器永远不要内联的内容比总是内联的内容要多得多(衡量改进)。它的侵入性也往往要小得多,并且在分析变化时可以产生更多可预测和可理解的热点。
虽然通过告诉编译器什么不要内联来尝试提高性能似乎非常违反直觉并且有些落后,但根据我的经验,我认为它与优化编译器的工作方式更加协调,并且对他们的代码的侵入性要小得多一代。要记住的一个很容易忘记的细节是:
内联 a
callee
通常会导致caller
, 或 , 的调用者caller
停止内联。
这就是强制内联对代码生成进行相当侵入性更改的原因,这可能会在您的分析会话中产生混乱的结果。我什至遇到过这样的情况,强制内联在多个地方重复使用的函数以非常令人困惑的方式完全重新洗牌了所有前十个热点,其中自采样率最高。有时,我觉得我在与优化器作斗争,使一件事在这里变得更快,只是为了在同样常见的用例中交换其他地方的减速,特别是在字节码解释等优化器的棘手情况下。我发现noinline
一些方法更容易成功地用于消除热点,而无需在其他地方交换另一个热点。
如果我们可以在调用站点内联而不是确定是否对函数的每个调用都应该内联,那么内联函数的侵入性就会大大降低。不幸的是,除了 ICC 之外,我还没有找到很多支持这种功能的编译器。如果我们通过在调用站点内联而不是强制内联特定函数的每次调用来响应热点,这对我来说更有意义。由于缺乏大多数编译器的广泛支持,我在
noinline
.
优化noinline
所以优化 with 的想法noinline
仍然是为了同一个目标:帮助优化器内联我们最关键的功能。不同之处在于,我们不是试图通过强制内联它们来告诉编译器它们是什么,而是相反,通过强制阻止它们被内联来告诉编译器哪些函数绝对不是关键执行路径的一部分。我们专注于识别罕见的非关键路径,同时让编译器仍然可以自由地确定在关键路径中内联的内容。
假设您有一个执行一百万次迭代的循环,并且有一个调用的函数baz
,即使它只有 5 行代码,平均每几千次迭代在该循环中很少调用一次,以响应非常不寻常的用户输入并且没有复杂的表达。您已经分析了此代码,分析器在反汇编中显示调用一个函数foo
,然后调用该函数baz
具有最大数量的样本,并且大量样本分布在调用指令周围。自然的诱惑可能是强制 inline foo
。我建议改为尝试将结果标记baz
为noinline
并计时。通过这种方式,我设法使某些关键循环的执行速度提高了 3 倍。
分析生成的程序集,加速来自foo
现在被内联的函数,因为不再baz
将调用内联到其主体中。
我经常发现在这样的情况下,将类比标记为比强制内baz
联noinline
产生更大的改进foo
。我不是计算机体系结构向导,无法准确理解原因,但在这种情况下查看分析器中的反汇编和样本分布,强制内联的结果foo
是编译器仍在内联很少执行baz
的顶部foo
,使得foo
通过仍然内联罕见情况的函数调用,比必要的更臃肿。baz
通过简单地用标记noinline
,我们允许foo
在没有实际内联的情况下被内联baz
。为什么内联会产生额外的代码baz
以及减慢整体功能仍然不是我准确理解的;根据我的经验,到更远的代码路径的跳转指令似乎总是比更近的跳转花费更多的时间,但我不知道为什么(可能与跳转指令在更大的操作数上花费更多时间有关做指令缓存)。我可以肯定地说,noinline
在这种情况下偏爱提供了卓越的性能来强制内联,并且在随后的分析会话中也没有这样的破坏性结果。
所以无论如何,我建议先noinline
尝试一下,然后再强制内联。
人类与优化器
在什么情况下假设我在这个问题上比我的编译器更了解?
我会避免如此大胆地假设。至少我还不够好做那件事。如果有的话,多年来我已经了解到一个令人羞愧的事实,即一旦我检查和测量我用分析器尝试的东西,我的假设往往是错误的。我已经超越了这个阶段(几十年来让我的分析器成为我最好的朋友),以避免在黑暗中完全盲目地刺伤,只是为了面对卑微的失败并恢复我的改变,但在我最好的情况下,我仍然在做,在大多数,有根据的猜测。尽管如此,我总是比我的编译器更了解,希望我们大多数程序员总是比我们的编译器更了解这一点,我们的产品应该如何设计以及我们的客户最有可能如何使用它. 这至少让我们在理解编译器不具备的常见情况和罕见情况的代码分支方面有了一些优势(至少在没有 PGO 的情况下,我从来没有用 PGO 获得过最好的结果)。编译器不具备这种类型的运行时信息和对常见用户输入的预见性。正是当我将这些用户端知识和手头的分析器结合起来时,我才发现最大的改进是在这里和那里推动优化器教它内联什么,或者在我的情况下更常见的是什么从不内联。