我想用 openMP 优化递归。所以,我从这个问题开始: 使用 openmp 并行化此递归的最佳方法
在寻找优化递归函数时,我首先对 openMP 感兴趣。从我在那里找到的下面的代码开始(https://en.cppreference.com/w/cpp/chrono):
#include <iostream>
#include <chrono>
long fibonacci(unsigned n)
{
if (n < 2) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
int main()
{
auto start = std::chrono::steady_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}
在没有任何优化的情况下用 g++ 编译它给出了结果:
f(42) = 267914296
elapsed time: 1.88232s
然后我使用 openMP 类似于上面的答案......但我不得不停止这个过程,因为它花费了非常非常长的时间。我对 openMP 递归并行性了解不多,因为我只是用它来优化我代码中的循环......另外,我在 gcc 文档中找到了一些东西:
__attributes__((const))
然后我将它添加到我的 openMP“优化”版本,但我得到的只是一个核心转储!!!
所以我删除了我的 omp pragmas 以检查核心转储是由于我的代码还是其他原因......
然后代码变成了:
#include <iostream>
#include <chrono>
__attribute__ ((const))
long fibonacci(unsigned n)
{
int long r[2];
if (n < 2) return n;
r[0]=fibonacci(n-1);
r[1]=fibonacci(n-2);
return (r[0]+r[1]);
}
int main()
{
auto start = std::chrono::steady_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}
我用以下命令编译它:
g++ fibonacci.cpp -o fibonacci -Wall -O2 -march=native
现在执行 42:
的斐波那契计算所需的时间少了很多f(42) = 267914296
elapsed time: 0.00106504s
省电模式 在性能模式下它给
f(42) = 267914296
elapsed time: 0.000187806s
这是我电脑的 lscpu(如果有人想比较结果):
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
CPU family: 6
Model: 158
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
Stepping: 9
CPU(s) scaling MHz: 95%
CPU max MHz: 4500,0000
CPU min MHz: 800,0000
BogoMIPS: 8403,00
现在,我不知道 openMP 需要多少时间,因为我无法获得令人满意的结果......我希望有人能找到一种方法将 omp pragmas 添加到这种递归案例中。
根据需要,这里是这个案例的omp版本1:
#include <iostream>
#include <chrono>
#include <omp.h>
__attribute__ ((const))
long fibonacci(unsigned n)
{
int long a, b;
if (n < 2) return n;
#pragma omp parallel
#pragma omp single nowait
{
#pragma omp task shared(a)
a=fibonacci(n-1);
#pragma omp task shared(b)
b=fibonacci(n-2);
#pragma omp taskwait
}
return (a+b);
}
int main()
{
auto start = std::chrono::steady_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}
让我陷入“傻代码”的版本:
#include <iostream>
#include <chrono>
#include <omp.h>
__attribute__ ((const))
long fibonacci(unsigned n)
{
int long r[2];
int i;
if (n < 2) return n;
#pragma omp parallel
#pragma omp single
for(i=0;i<2;++i)
{
#pragma omp task shared(r)
r[i]=fibonacci(n-i-1);
}
return (r[0]+r[1]);
}
int main()
{
auto start = std::chrono::steady_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}
这些 omp 版本的构建命令是:
g++ omp_fibonacci_for.cpp -o omp_fibonacci_for -Wall -fopenmp
他们两个似乎都“永远”循环(幸运的是 Ctrl-C 工作正常)。 但是,在写这个问题的结尾时,第二个带有“for”的版本,处理成功:
f(42) = 267914296
elapsed time: 480.953s
明确地说,我不是在寻找“最佳性能”,我的目标是尝试获得一个在合理时间内处理的 omp 版本,目的是找到在递归代码上使用 openMP 的方法。老实说,我期待比 480s 更好的执行时间!我也很惊讶它在“糟糕的调试”版本中花费的时间少于 0.001s 来处理......
现在我想知道如果我将这个模型应用于“havier”任务或者我做错了什么,我使用 openMP 的方式是否可以接受。
如果你想快速计算斐波那契数列,你通常希望从使用迭代而不是递归开始。
unsigned long long fib(unsigned input) {
unsigned long long old1 = 0;
unsigned long long old2 = 1;
for (int i = 1; i < input; i++) {
unsigned long long sum = old1 + old2;
old1 = old2;
old2 = sum;
}
return old2;
}
请注意,我对此做了一些更改,始终使用 64 位 unsigned long long,因此我们可以使用更大的数字进行测试。 64 位数字足以计算 F(93)。
做一些测试,
high_resolution_clock
(至少在我的系统上)可以测量的最短时间间隔约为 427 纳秒。如果我在关闭优化的情况下编译它,并计算 F(93),它通常说它在 427ns 内运行,但有时在 855ns 内运行。如果我打开优化,那么时间意味着什么,它通常说 0ns,但偶尔会说 427ns。
做一些计算,我猜它真的更像是 30-35ns(每次迭代大约 1 个时钟,93 次迭代/2.8 GHz)。在你的 CPU 上可能接近 20-25ns。
我发现使用线程处理这个任务有两个问题。
首先,当你拥有彼此独立的事物时(例如,一个循环,其中循环的每次迭代都执行相同的操作,但它们中的每一个都在不同的数据上),线程工作得最好。斐波那契数与此相反——每次计算都取决于前一次计算的结果,因此很难并行进行。
其次,使用线程会增加一些开销来创建线程、向它们传递数据、从它们收集数据等等。要使线程正常工作,您需要完成足够的工作,使每个线程完成的工作比让它完成该工作的开销要多得多。同样,这几乎与本例相反。在一个典型的系统上,创建一个线程所花费的时间比上面的代码计算 F(93) 所花费的时间要长得多。
尝试将线程用于这项工作就像尝试在一张纸上写一个字,由几个人各自写一封信的一部分,然后将纸传给下一个人。一个人可以用更少的时间写下整个单词,而不是传递一张纸。
你的实现很愚蠢。
电话
return fibonacci(n-1) + fibonacci(n-2);
应该是对(未)有序集合的查找。
因此,在考虑了所有评论和答案之后,这个新代码似乎是在递归代码上应用 openMP 编译指示的更好方法:
#include <iostream>
#include <chrono>
#include <omp.h>
__attribute__ ((const))
long fibonacci(unsigned n)
{
int long a, b;
if (n < 2) return n;
#pragma omp task shared(a)
a=fibonacci(n-1);
#pragma omp task shared(b)
b=fibonacci(n-2);
#pragma omp taskwait
return (a+b);
}
int main()
{
auto start = std::chrono::steady_clock::now();
long res;
#pragma omp parallel
{
#pragma omp single
res = fibonacci(42);
}
std::cout << "f(42) = " << res << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "elapsed time: " << elapsed_seconds.count() << "s\n";
}
构建命令:
g++ omp_fibonacci.cpp -o omp_fibonacci -Wall -fopenmp
输出:
f(42) = 267914296
elapsed time: 255.225s
@DanielLangr 评论特别有用。