在 MSVC C++ 中强制循环展开

Posted

技术标签:

【中文标题】在 MSVC C++ 中强制循环展开【英文标题】:Forcing loop unrolling in MSVC C++ 【发布时间】:2021-08-08 12:01:57 【问题描述】:

想象下面的代码:

for (int i = 0; i < 8; ++i) 
    // ... some code

我希望在 MSVC 中展开此循环。在 CLang 中,我可以在循环之前添加 #pragma unroll。但是如何在 MSVC 中做同样的事情呢?

我知道无论如何编译器都会为我展开这个循环,即使没有任何编译指示。但我想确定这一点,我想一直展开它。

强制展开的一种方法是使用传入函子的模板展开函数的递归调用,如下面的代码:

Try it online!

template <int N, int I = 0, typename F>
inline void Unroll(F const & f) 
    if constexpr(I < N) 
        f.template operator() <I> ();
        Unroll<N, I + 1>(f);
    


void f_maybe_not_unrolled() 
    int volatile x = 0;
    for (int i = 0; i < 8; ++i)
        x = x + i;


void f_forced_unrolled() 
    int volatile x = 0;
    Unroll<8>([&]<int I> x = x + I; );

但是如果没有像上面这样更困难的代码,是否可以在 MSVC 中强制展开?

CLang也有可能真的强制展开,我认为#pragma unroll只是给CLang一个提示(或者我不对),也许有类似#pragma force_unroll的东西,是吗?

我也想展开这个单一的循环,我不需要像传递命令行参数这样的解决方案来强制展开所有可能的循环。

注意: 对我来说,在所有 100% 情况下真正强制展开代码并不重要。我只需要它在大多数情况下发生。基本上我只是想找出与 CLang 的 #pragma unroll 相同的 MSVC,与不使用 pragma 相比,它平均使编译器更有可能展开循环。

【问题讨论】:

请注意,即使f_forced_unrolled 实际上也不是强制的。优化编译器可能仍然会说“嘿,线性代码看起来像一个循环,让我们把它变成一个循环吧”。如果你想要汇编,写汇编。 @MSalters 是的,这是事实。但至少现在如果您查看上面的 Try-it-online godbolt 链接的程序集,您会看到非强制函数在 CLang 中展开,但在 MSVC 中没有展开。但是强制展开函数在 clang 和 msvc 中都展开了。所以这意味着至少平均而言,我的展开功能比没有它提供更多的展开。此外,我认为可以通过在某些模板化 constexpr 上下文中的 Unroll 函数中使用 I 索引来真正强制展开,这意味着编译器将无法从中循环,因为 I 索引被用作constexpr. 是的,对于这段特定的代码,优化器通常会预测展开的版本在普通 CPU 上更快。但这取决于代码大小和缓存影响。关于constexpr,这对我的论点来说根本不重要。循环创建可能发生在代码生成阶段,此时原始 C++ 标记早已被遗忘。 @MSalters 至少如果不可能真正强制,那么我至少希望以更高的概率展开。现在常规循环不会被 MSVC 展开,而 Unroll-ed 循环被展开。实际上,我只是想找到我们的 MSVC 是否与 CLang 的#pragma unroll 相同,对我来说,现在平均来说就足够了。你知道 MSVC 有没有这样的 pragma 吗? 这感觉有点像 XY 问题。 IME,VC++ 优化器在展开方面非常好。如果它决定不展开某些东西,那是因为它确定这将是一个坏主意(例如,它觉得它可能会不必要地破坏 I-cache),而且它可能是正确的。您是否有兴趣将展开纯粹作为一种完美的东西,或者是否有一些更深层次的黑客隐藏在您的渴望之下? 【参考方案1】:

你不能直接。最接近的#pragma#pragma loop(...),它没有展开选项。这里的重头戏是 Profile Guided Optimization - 分析您的程序,MSVC 将知道此循环运行的频率。

【讨论】:

以上是关于在 MSVC C++ 中强制循环展开的主要内容,如果未能解决你的问题,请参考以下文章

强制/说服/欺骗 GCC 展开 _Longer_ 循环?

优化编译器如何决定何时展开循环以及展开循环的程度?

Loop Unrolling 循环展开

为啥 clang 无法展开循环(即 gcc 展开)?

循环展开有利的条件以及收益率下降的点?

C/C++ 中的自展开宏循环