我正在使用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类似,但不一样,因为我在询问如何在编译时使用相同的运行时效率代码,而不是为什么或如何编译时代码是提高效率。
这是完整的程序。
#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
答案 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 Coliru,constexpr
和非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
是替代品。