如何将此运行时效率函数转换为constexpr?

时间:2014-11-03 22:06:35

标签: c++ c++11 fibonacci constexpr

我正在使用C ++ constexpr在编译时评估此代码可以正确评估的计算的Fibonacci序列的最大项。为此,我使用以下代码:

constexpr unsigned long long fibo(unsigned n) {
    return (n < 2) ? 1 : fibo(n-1)+fibo(n-2);
}

constexpr unsigned max_fibo(unsigned n) {
    return (fibo(n+1) >= fibo(n)) ? max_fibo(n+1) : n;
}

const unsigned MAX_FIBO_TERM = max_fibo(0);

这很好用并快速构造MAX_FIBO_TERM的正确值(我机器上的92)。但是,使用fibo函数在运行时实际计算项是非常慢的(仅计算前48个项需要大约90秒)。

我可以使用替代函数来计算更快的术语:

unsigned long long fiboiter(unsigned n, unsigned long long &prev, 
                            unsigned long long &prevprev) {
    return prev = (n < 2) ? prevprev = 1 : 
        (std::swap(prev, prevprev), prev + prevprev);
}

但是,我似乎无法将其变为有效的constexpr函数。 (这就是为什么它以那种奇怪的方式写的。)当我运行这个版本时,我的机器需要0.005秒。

当我使用g++ -O2 -std=c++11 fibo.cpp -o fibo进行编译时,只需0.302秒。

我的问题是:如何在运行时和编译时使用相同的高效功能?

我在64位Linux机器上使用g ++ 4.8.3,但我想要的是一个跨平台的通用答案,而不仅仅是一个特定于此编译器和机器的答案。不,在这种情况下,C ++ 14还不是一个选择。

另外,我的问题与this question类似,但不一样,因为我在询问如何在编译时使用相同的运行时效率代码,而不是为什么或如何编译时代码是提高效率。

这是完整的程序。

fibo.cpp

#include <iomanip>
#include <iostream>
#include <locale>

constexpr unsigned long long fibo(unsigned n) {
    return (n < 2) ? 1 : fibo(n-1)+fibo(n-2);
}   
unsigned long long fiboiter(unsigned n, unsigned long long &prev, 
                            unsigned long long &prevprev) {
    return prev = (n < 2) ? prevprev = 1 : 
        (std::swap(prev, prevprev), prev + prevprev);
}    
constexpr unsigned max_fibo(unsigned n) {
    return (fibo(n+1) >= fibo(n)) ? max_fibo(n+1) : n;
}    
const unsigned MAX_FIBO_TERM = max_fibo(0);

int main(int argc, char **)
{
    // format numeric output according to user's preferred locale
    std::cout.imbue(std::locale{""});
    unsigned long long prev, prevprev;

    if (argc > 1) {
        for (unsigned i = 0; i <= MAX_FIBO_TERM; ++i) 
            std::cout << "fib(" << std::setw(2) << i << ") = " 
                      << std::setw(26) << fiboiter(i, prev, prevprev) << '\n';
    } else {
        for (unsigned i = 0; i <= MAX_FIBO_TERM; ++i) 
            std::cout << "fib(" << std::setw(2) << i << ") = " 
                      << std::setw(26) << fibo(i) << '\n';
    }
}

样本输出

fib( 0) =                          1
fib( 1) =                          1
fib( 2) =                          2
fib( 3) =                          3
fib( 4) =                          5
fib( 5) =                          8
 ...
fib(89) =  2,880,067,194,370,816,120
fib(90) =  4,660,046,610,375,530,309
fib(91) =  7,540,113,804,746,346,429
fib(92) = 12,200,160,415,121,876,738

2 个答案:

答案 0 :(得分:8)

好的编译器可以很好地优化尾递归,尝试尾递归constexpr实现:

constexpr unsigned long long fibo(unsigned n,
                                  unsigned long long prev = 1,
                                  unsigned long long prevprev = 0) {
    return n == 0 ? prev : fibo(n - 1, prev + prevprev, prev);
}   

According to the clang on Coliruconstexpr和非constexpr版本都需要5-6毫秒才能完成(并且可能是I / O占主导地位)。

答案 1 :(得分:1)

template<size_t max, class F, size_t...is, class R=std::result_of_t<F(unsigned)>>
R memoize(std::index_sequence<is...>,unsigned val){
  static R cache[]={ F{}(is)...};
  if (val<max)return cache[val];
  return F{}(val);
}
template<size_t max, class F, class R=std::result_of_t<F(unsigned)>>
R memoize(unsigned val){
  return memoize<max,F>( std::make_index_sequence<max>{}, val );
}
struct fib_t{
  constexpr auto operator()(unsigned val)const{
    return fibo(val);
  }
  constexpr fib_t() {}
};

然后memoize<100, fib_t>(x)将在运行时快速达到100。

更快的fibo会使其在100后更快。

虽然index_sequence是C ++ 14,但这里有一个替代品,最多可以使用100个元素:

template<std::size_t...>struct indexes{using type=indexes;};
template<std::size_t M, std::size_t...Is>struct make_indexes_t:make_indexes_t<M-1,M-1,Is...>{};
template<std::size_t...Is>struct make_indexes_t<0,Is...>:indexes<Is...>{};
template<std::size_t M>using make_indexes=typename make_indexes_t<M>::type;

并将std::make_index_sequence替换为make_indexes,将std::index_sequence替换为indexes

如果你需要超过100的答案,你需要比上面更强大的make_indexes。在野外有许多实现,它们主要由分而治之(写连接,并通过递归分割范围和连接来编写从min到max的make_indexes。这给出了对数模板递归深度)。

std::result_of_t是C ++ 14,但template<class Sig>using result_of_t=typename std::result_of<Sig>::type是替代品。