我可以依靠编译器查找和优化简单的布尔循环不变量吗?
Can I rely on the compiler finding and optimizing simple boolean loop invariants?
我有一个类似于下面的循环,它有一个不变量,这里是 scaleEveryValueByTwo
的不变值。 我能否依靠编译器找到这个不变性而不在每次迭代中检查条件(基本上编译为类似于底部代码的东西)?
void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
if (scaleEveryValueByTwo)
{
x *= 2;
}
xs.push_back(x);
}
}
我当然可以手动将它分成两个循环(见下文)或将缩放部分放在一个单独的函数中,但在很多情况下,这会使代码更长,而且在我看来更难阅读(例如,如果我有 3D 数据所有维度的嵌套循环我会复制所有三行循环 headers 和最多六行花括号)。
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)的优化,而不是一些可能缺少大多数事实上标准的优化的奇异编译器编译器。
以前MSVC编译器中有/Og(全局优化),现在默认开启。
我的猜测是其他编译器也这样做。
要了解循环优化是如何完成的,请查看下方 link 并搜索 "Loop optimization"
https://docs.microsoft.com/en-us/cpp/build/reference/og-global-optimizations?view=vs-2019
因为现在这是默认的,你可以依赖编译器。
您可以将 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()
returns。)
然后您调用 loadValuesFromDisk<true>()
或 loadValuesFromDisk<false>()
。如果 scaleEveryValueByTwo
仅在运行时已知,则可以分支到适当的函数:
void loadValuesFromDisk(bool const scaleEveryValueByTwo)
{
if (scaleEveryValueByTwo)
loadValuesFromDisk<true>();
else
loadValuesFromDisk<false>();
}
我有一个类似于下面的循环,它有一个不变量,这里是 scaleEveryValueByTwo
的不变值。 我能否依靠编译器找到这个不变性而不在每次迭代中检查条件(基本上编译为类似于底部代码的东西)?
void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
if (scaleEveryValueByTwo)
{
x *= 2;
}
xs.push_back(x);
}
}
我当然可以手动将它分成两个循环(见下文)或将缩放部分放在一个单独的函数中,但在很多情况下,这会使代码更长,而且在我看来更难阅读(例如,如果我有 3D 数据所有维度的嵌套循环我会复制所有三行循环 headers 和最多六行花括号)。
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)的优化,而不是一些可能缺少大多数事实上标准的优化的奇异编译器编译器。
以前MSVC编译器中有/Og(全局优化),现在默认开启。 我的猜测是其他编译器也这样做。
要了解循环优化是如何完成的,请查看下方 link 并搜索 "Loop optimization"
https://docs.microsoft.com/en-us/cpp/build/reference/og-global-optimizations?view=vs-2019
因为现在这是默认的,你可以依赖编译器。
您可以将 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()
returns。)
然后您调用 loadValuesFromDisk<true>()
或 loadValuesFromDisk<false>()
。如果 scaleEveryValueByTwo
仅在运行时已知,则可以分支到适当的函数:
void loadValuesFromDisk(bool const scaleEveryValueByTwo)
{
if (scaleEveryValueByTwo)
loadValuesFromDisk<true>();
else
loadValuesFromDisk<false>();
}