【问题标题】:Can I rely on the compiler finding and optimizing simple boolean loop invariants?我可以依靠编译器查找和优化简单的布尔循环不变量吗?
【发布时间】:2019-04-15 20:26:18
【问题描述】:

我有一个类似下面的循环,它有一个不变量,这里scaleEveryValueByTwo 的值永远不会改变。 我可以依靠编译器找到这个不变量,而不是在每次迭代中检查条件(本质上是编译成与底部代码类似的东西)吗?

void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
    std::vector<MyValueType> xs;
    while(fileHasNewValues())
    {
        auto x = loadNextValue();
        if (scaleEveryValueByTwo)
        {
            x *= 2;
        }
        xs.push_back(x);
    }
}

我当然可以手动将其拆分为两个循环(见下文)或将缩放部分放在单独的函数中,但在许多情况下,这会使代码更长并且在我看来更难阅读(例如,如果我有3D 数据所有维度的嵌套循环我将复制所有三行循环标题和最多六行花括号)。

void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
    std::vector<MyValueType> xs;
    while(fileHasNewValues())
    {
        auto x = loadNextValue();
        xs.push_back(x);
    }

    if (scaleEveryValueByTwo)
    {
        for(auto &x : xs)
        {
            x *= 2;
        }
    }
}

我主要感兴趣的是我是否可以依赖这个(或者更好的是,强制执行)这个优化,用于 gcc 或 MSVC 等常用编译器,而不是一些可能缺少大多数编译器事实上标准的优化的奇异编译器。

【问题讨论】:

  • 很有可能。即使它没有,分支预测器也会快速学习它并且性能是一样的。
  • 一般的经验法则是,编译器会做出所有你能想到的优化以及一堆你甚至没有想到的其他优化。
  • @NathanOliver 是正确的...编译器很可能会执行此优化如果这是一个好主意。在分析结果的上下文之外提出这类问题几乎可以肯定是过早的优化(即错误的问题)。

标签: c++ compiler-optimization


【解决方案1】:

以前在 MSVC 编译器中有 /Og(全局优化),现在默认启用。 我的猜测是其他编译器也这样做。

要了解循环优化是如何完成的,请查看以下链接并搜索“循环优化”

https://docs.microsoft.com/en-us/cpp/build/reference/og-global-optimizations?view=vs-2019

现在默认情况下,您可以依赖编译器。

【讨论】:

  • 这让我走上了正轨,其他编译器似乎也有类似的选择,例如-fmove-loop-invariants 和一些更激进的 gcc
【解决方案2】:

您可以将scaleEveryValueByTwo 设置为模板参数,以确保条件只计算一次。 在 C++17 中,您可以使用 if constexpr 如下

template <bool scaleEveryValueByTwo>
void loadValuesFromDisk()
{
    std::vector<MyValueType> xs;
    while(fileHasNewValues())
    {
        auto x = loadNextValue();
        if constexpr (scaleEveryValueByTwo)
        {
            x *= 2;
        }
        xs.push_back(x);
    }
}

如果你还没有C++17,上面的代码可以得到,例如,通过一个辅助模板函数multiply如下

template <bool activate>
void multiply(decltype(loadNextValue())& x);

template <>
void multiply<true>(decltype(loadNextValue())& x) { x *= 2; }

template <>
void multiply<false>(decltype(loadNextValue())& x) { }

template <bool scaleEveryValueByTwo>
void loadValuesFromDisk()
{
    std::vector<MyValueType> xs;
    while(fileHasNewValues())
    {
        auto x = loadNextValue();
        multiply<scaleEveryValueByTwo>(x);
        xs.push_back(x);
    }
}

(注意:我使用decltype 是因为我不知道你的例程loadNextValue() 会返回什么。)

然后您拨打loadValuesFromDisk&lt;true&gt;()loadValuesFromDisk&lt;false&gt;()。如果scaleEveryValueByTwo 仅在运行时已知,您可以分支到相应的函数:

void loadValuesFromDisk(bool const scaleEveryValueByTwo)
{
    if (scaleEveryValueByTwo)
        loadValuesFromDisk<true>();
    else
        loadValuesFromDisk<false>();
}

【讨论】:

  • C++17 之前的情况不需要 if constexprif (/* pure constexpr constant */) { 肯定会被编译器优化掉。
猜你喜欢
  • 2017-02-05
  • 2017-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多