c++ - 为什么编译时执行比运行时执行快得多?
问题描述
与这个问题所说的相反,这段代码表现出一些奇怪的行为:
long long int fibonacci(int num) {
if (num <= 2) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}
int main() {
auto t1 = std::chrono::high_resolution_clock::now();
long long int x = fibonacci(45);
auto t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> time(t2 - t1);
std::cout << "Time taken: " << time.count() << "ms";
}
在我的机器上,它使用 (GCC) 在 ~700ms 内编译,-O3
输出为:
Time taken: 2667.55ms
我重写了上面的代码,constexpr
如下所示:
constexpr long long int fibonacci(int num) {
if (num <= 2) return 1;
return fibonacci(num - 1) + fibonacci(num - 2);
}
int main() {
auto t1 = std::chrono::high_resolution_clock::now();
constexpr long long int x = fibonacci(45);
auto t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> time(t2 - t1);
std::cout << "Time taken: " << time.count() << "ms";
}
编译时间大致相同,但输出为:
Time taken: 0ms
就目前而言,fibonacci(45)
在编译时评估比在运行时评估要快得多。为了消除多核编译的原因(这绝对不是),我用fibonacci(60)
. 同样,代码在相同的时间内编译,但输出为:
Time taken: 29499.6ms
是什么导致了如此大的性能差距?
解决方案
基本上,对于那一小段代码,编译时间并不重要。
甚至,如果您进行编译时评估。
主要问题是这里使用的最糟糕的算法。使用 2 个递归调用,然后再次调用 2 个递归函数,依此类推,这样一个简单算法的时间复杂度确实是最坏的情况。
这个问题可以而且必须通过迭代方法来解决。
像这样的东西:
unsigned long long fibonacci(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
如果你使用这个函数,那么,不管优化与否,你的 main 将在 1ms 以下运行。
在你原来的主函数中,如果你不使用计算结果,所以变量“X”,那么优化编译器可以消除完整的函数调用。没有必要调用该函数。结果未使用。
进行以下实验。
添加std::cout << x << '\n';
为 main 函数中的最后一条语句。你会感到惊讶。启用优化后,函数将在最后被调用。而你的时间测量什么也没有。
现在到编译器时间版本。此外,编译器将在内部使用优化代码。它将无意义的递归方法转换为迭代方法。并且计算该值将比编译器开销函数花费更少的时间。
所以,这才是真正的原因。
并且斐波那契数字总是可以在编译时完全编译。64 位结果值只有 93 个可能的结果。
请参阅以下方法,该方法创建所有斐波那契数的编译时间数组。如果你想要第 n 个数字,它只是一个查找值。
这将在非优化或优化模式下运行得非常快。
这是解决该问题的禁食可能解决方案。
#include <iostream>
#include <utility>
#include <array>
#include <algorithm>
#include <iterator>
// All done during compile time -------------------------------------------------------------------
constexpr unsigned long long getFibonacciNumber(size_t index) noexcept {
unsigned long long f1{ 0ull }, f2{ 1ull }, f3{};
while (index--) { f3 = f2 + f1; f1 = f2; f2 = f3; }
return f2;
}
// Some helper to create a constexpr std::array initilized by a generator function
template <typename Generator, size_t ... Indices>
constexpr auto generateArrayHelper(Generator generator, std::index_sequence<Indices...>) {
return std::array<decltype(std::declval<Generator>()(size_t{})), sizeof...(Indices) > { generator(Indices)... };
}
template <size_t Size, typename Generator>
constexpr auto generateArray(Generator generator) {
return generateArrayHelper(generator, std::make_index_sequence<Size>());
}
constexpr size_t MaxIndexFor64BitFibonacci = 93;
// This is the definition of a std::array<unsigned long long, 93> with all Fibonacci numbers in it
constexpr auto Fib = generateArray<MaxIndexFor64BitFibonacci>(getFibonacciNumber);
// End of: All done during compile time -----------------------------------------------------------
// The only code executed during runtime
int main() {
// Show all Fibonacci numbers up to border value
for (const auto fib : Fib) std::cout << fib << '\n';
}
推荐阅读
- python - 我怎么能继续实现 tkinter.filedialog.askdirectory() 呢?
- php - 如何在 Laravel 中对相关模型应用搜索查询?
- vue.js - 通过使用参数作为路由,Vue.js 路由不起作用
- android - 为什么 Kotlin 循环这么慢?
- c# - 如何修复 Visual Studio 上的内部 500 错误
- r - 如何在 R 中拟合多元正态分布?
- angular - 在组件中更新数据后使用解析器在子组件中加载数据
- regex - Rails 6- 符合 RFC5322 的电子邮件验证
- angular - 将电子邮件模板创建器 (paperbits.io) 应用于 Angular 6+
- python - cuDF 用于字符串比较提升