23

在旧版本中测试代码gcc-4.4.0gcc-4.6.4编译器能够应用智能优化并预先计算输入的结果const

#include <iostream>
#include <chrono>
using namespace std;

const auto N = 1000000000ULL;  // constexptr is tested, no effect

unsigned long long s(unsigned long long n)
{
    auto s = 0ULL;
    for (auto i = 0ULL; i < n; i++)
        s += i;
    return s;
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();

    auto x = s(N);

    auto t2 = std::chrono::high_resolution_clock::now();
    auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(t2-t1).count();
    cout << "Result: " << x << " -- time (ms):" << t/0.1e7 << endl;
}

N是一个常数值,然后编译器可以s在编译时运行函数并将结果分配给x. (不需要运行时计算N

产生不同版本的 gcc(以及一个版本的 clang):

  • 最新版本(clang-3.4结果(预先计算)0.001532 ms:.
  • 旧版本(gcc-4.4.0结果(预先计算)0.013517 ms:.
  • 旧版本(gcc-4.6.4结果(预先计算)0.001 ms:.
  • 较新的版本(gcc-4.8.0+)不会在编译时计算它,结果1313.78 ms!!。

问题:

  • 4.8.1 中是否省略了此优化?为什么?
  • 是否有任何编译器命令/开关来启用它(如果它默认禁用)?
  • 如果省略,如何强制编译器进行此预计算?

 


注意(1):我测试了两个-O2-O3开关,没有效果。

注意(2):强制,我的意思是编译器的命令和开关。

注(3):函数s只是一个例子,可以用更复杂的函数代替。

4

4 回答 4

13

我已将其作为错误提交。是的,这是 4.8 版中的回归,5 周前在较新的版本中修复。在这里关注它:

于 2013-10-14T07:23:24.117 回答
7

constexpr您可以使用新的 C++11关键字强制它在编译时运行。

首先,您必须将迭代转换为递归(在 C++1y 中删除了此要求),例如:

constexpr unsigned long long s(unsigned long long n)
{
    return n? n + s(n-1): 0;
}

或者使用尾递归(当输入可变时仍然适用于运行时计算):

constexpr unsigned long long s_impl( unsigned long long accum, unsigned long long n, unsigned long long n_max )
{
    return (n < n_max)? s_impl(accum + n + 1, n + 1, n_max): accum;
}
constexpr unsigned long long s(unsigned long long n)
{
    return s_impl(0, 0, n);
}

(在 C++1y 中,您需要做的就是将constexpr关键字添加到现有实现中)

然后调用它

constexpr auto x = s(N);
于 2013-10-13T20:55:18.463 回答
3

在编译时处理计算的 C++11 方法是使用constexpr. 可悲的是,constexpr功能在可以做的事情上有些限制。在 C++11 中,constexpr允许一个函数包含空语句、static_assert()声明、typedefs 和using声明/指令,并且只包含一个return-statement(我暂时感到困惑,因为我正在查看规则放松的 C++14 草案)。也就是说,您需要递归地制定您的函数。从好的方面来说,如果constexpr使用常量表达式调用函数,它将在编译时进行评估。

除此之外,您可能希望通过常量折叠来帮助编译器。例如,它可以帮助

  1. 使函数s()成为inline函数。
  2. 声明Nconstexpr unsigned long long N = 1000000000ULL;
  3. 确保使用合适的优化级别。
于 2013-10-13T20:56:45.640 回答
2

4.8.1 中是否省略了此优化?

看起来它已经消失了。但它仍然存在于 4.7.2 中。

为什么?[来自您的一条评论:] 我认为优化非常出色,并且不会造成任何伤害。

这很可能是偶然的,gcc 开发人员对此一无所知。

我可以想出一个很好的理由,为什么我至少要为这种优化提供一个上限。早在 2009 年我就被 MSVC 咬了:当我给它一个机器生成的 C 代码时,它试图优化它,而编译器在它上面挣扎了几分钟。显然,它正在拼命地尝试做一些应该以某种方式受到限制的优化,这样编译器就不会在 7KB 的源文件上挣扎几分钟。我的观点是:您可能希望限制可能过多地增加编译时间的优化。

然而,这里似乎并非如此。我已经用相当小N的 s 尝试过,也没有执行这个优化。

如果省略,如何强制编译器进行此预计算?
注(2):强制,我的意思是编译器的命令和开关

我无法欺骗 gcc 4.8.1 进行此优化。如果没有人很快说这是一个已知问题或者可以使用一些编译器标志启用它,我将提交一个错误报告。

于 2013-10-13T22:34:02.723 回答