问题描述
据我所知,关键字 windows push notifications system service
告诉编译器表达式的计算可以在编译时发生。具体来说,变量上的 constexpr
意味着可以在编译时评估变量的值,而函数上的 constexpr
意味着可以在编译时调用该函数并评估其返回值。如果函数在运行时被调用,它只是作为一个普通函数。
今天写了一段代码尝试使用constexpr
:
constexpr
我原以为代码的编译会花费很多时间,但我错了。编译只用了0.0290s:
#include <iostream>
using namespace std;
constexpr long int fib(int n)
{
return (n <= 1)? n : fib(n-1) + fib(n-2);
}
int main ()
{
constexpr long int res = fib(32);
// const long int res = fib(32);
cout << res << endl;
return 0;
}
但如果我将 $ time g++ test.cpp
real 0m0.290s
user 0m0.252s
sys 0m0.035s
改为 constexpr long int res = fib(32);
,令我惊讶的是,它花费了更多的时间进行编译:
const long int res = fib(32);
总之,似乎 $ time g++ test.cpp
real 0m5.830s
user 0m5.568s
sys 0m0.233s
使得函数 const
在编译时被评估,但是 fib(32)
使它在运行时被评估.我真的很困惑。
我的系统:Ubuntu 18.04
我的 gcc: g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
解决方法
通过 inspecting 生成的程序集,我们可以确认在两种情况 G++ 7.5 在编译时计算了 fib(32)
值:
movl $2178309,%esi
G++ 如此快速地评估 constexpr
上下文的原因在于它在评估 constexpr
和 template
上下文时执行的 memoization。
记忆化将斐波那契计算复杂度降低到 O(N) 复杂度,从而完全消除了它。
那么,为什么非constexpr
评估会如此慢?我认为这是优化器中的错误/缺点。如果我尝试使用 G++ 8.1 或更高版本,编译时间没有区别,所以大概已经解决了。
较短的编译时间并不能证明在编译时未评估调用。您可以查看编译后的程序集,看看它实际上是在编译时使用 constexpr
在我的测试中进行评估的:https://godbolt.org/z/vbWaxe
在我使用较新编译器进行的测试中,const
并不比 constexpr
慢得多。这可能是您的编译器版本的实现质量问题。
快速编译时间评估的秘诀在于,只有极少数斐波那契数符合当今最大的数据类型 unsigned long long。
斐波那契数计算的基本信息是:根本不需要运行时的任何计算!一切都可以而且应该在编译时完成。然后,可以使用简单的查找机制。那永远是最有效、最快捷的解决方案。
因此,使用 Binet 公式,我们可以计算出只有很少 Fibonacci 数适合 C++ unsigned long long
数据类型,该数据类型现在在 2021 年通常为 64 位,并且是“最大”的可用数据类型。 环岛 93。这在今天是一个非常低的数字。
借助现代 C++ 17(及更高版本)的功能,我们可以在编译时轻松地为 64 位数据类型创建所有斐波那契数的 std::array
。因为如上所述,只有 93 个数字。
因此,我们将只为查找数组花费 93*8= 744 BYTE 的非运行时内存。那真是微不足道。而且,编译器可以快速获取这些值。
经过编译时间计算,我们可以很容易的通过写出FIB[n]
得到斐波那契数n。详细解释见下文。
而且,如果我们想知道一个数字是否是斐波那契数,那么我们使用 std::binary_search
来查找值。所以,这个函数将是例如:
bool isFib(const unsigned long long numberToBeChecked) {
return std::binary_search(FIB.begin(),FIB.end(),numberToBeChecked);
}
FIB(当然可以使用任何其他名称)是编译时间,constexpr std::array
。那么,如何构建该数组?
我们首先将计算斐波那契数的默认方法定义为 constexpr
函数(非递归):
// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
// Initialize first two even numbers
unsigned long long f1{ 0 },f2{ 1 };
// Calculating Fibonacci value
while (index--) {
// get next value of Fibonacci sequence
unsigned long long f3 = f2 + f1;
// Move to next number
f1 = f2;
f2 = f3;
}
return f2;
}
这样,可以在编译时轻松地将斐波那契数计算为 constexpr values
。然后,我们用所有斐波那契数填充 std::array
。我们还使用 constexpr
并使其成为带有可变参数包的模板。
我们使用 std::integer_sequence
为指数 0,1,2,3,4,5,.... 创建一个斐波那契数。
这很直接,并不复杂:
template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t,ManyIndices...>) noexcept {
return std::array<unsigned long long,sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};
该函数将输入一个整数序列 0,... 并返回一个 std::array<unsigned long long,...>
和相应的斐波那契数列。
我们知道最多可以存储 93 个值。因此我们创建了一个 next 函数,它将使用整数序列 1,...,92,93 调用上面的函数,如下所示:
constexpr auto generateArray() noexcept {
return generateArrayHelper(std::make_integer_sequence<size_t,MaxIndexFor64BitValue>());
}
现在,终于,
constexpr auto FIB = generateArray();
会给我们一个名为 FIB 的编译时 std::array<unsigned long long,93>
,其中包含所有的斐波那契数。如果我们需要第 i 个斐波那契数,那么我们可以简单地写成 FIB[i]
。运行时不会进行计算。
整个示例程序如下所示:
#include <iostream>
#include <array>
#include <utility>
#include <algorithm>
#include <iomanip>
// ----------------------------------------------------------------------
// All the following will be done during compile time
// Constexpr function to calculate the nth Fibonacci number
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
// Initialize first two even numbers
unsigned long long f1{ 0 },f2{ 1 };
// calculating Fibonacci value
while (index--) {
// get next value of Fibonacci sequence
unsigned long long f3 = f2 + f1;
// Move to next number
f1 = f2;
f2 = f3;
}
return f2;
}
// We will automatically build an array of Fibonacci numbers at compile time
// Generate a std::array with n elements
template <size_t... ManyIndices>
constexpr auto generateArrayHelper(std::integer_sequence<size_t,sizeof...(ManyIndices)>{ { getFibonacciNumber(ManyIndices)... } };
};
// Max index for Fibonaccis that for an 64bit unsigned value (Binet's formula)
constexpr size_t MaxIndexFor64BitValue = 93;
// Generate the required number of elements
constexpr auto generateArray()noexcept {
return generateArrayHelper(std::make_integer_sequence<size_t,MaxIndexFor64BitValue>());
}
// This is an constexpr array of all Fibonacci numbers
constexpr auto FIB = generateArray();
// All the above was compile time
// ----------------------------------------------------------------------
// Check,if a number belongs to the Fibonacci series
bool isFib(const unsigned long long numberToBeChecked) {
return std::binary_search(FIB.begin(),numberToBeChecked);
}
// Test
int main() {
const unsigned long long testValue{ 498454011879264ull };
std::cout << std::boolalpha << "Does '" <<testValue << "' belong to Fibonacci series? --> " << isFib(testValue) << "\n\n";
for (size_t i{}; i < 10u; ++i)
std::cout << i << '\t' << FIB[i] << '\n';
return 0;
}
使用 Microsoft Visual Studio Community 2019 版本 16.8.2 开发和测试
使用 gcc 10.2 和 clang 11.0.1 额外测试
语言:C++ 17