我正在努力理解OpenMP的task的工作方式。
因此,我从可能最简单的测试开始,遵循OpenMP 4.5的斐波纳契计算示例:
// Listing 1
#include <omp.h>
#include <stdio.h>
long fib(int n)
{
int i, j;
if (n<2)
return n;
else {
#pragma omp task shared(i)
i=fib(n-1);
#pragma omp task shared(j)
j=fib(n-2);
#pragma omp taskwait
return i+j;
}
}
int main()
{
#pragma omp parallel
#pragma omp single
{
long res = fib(27);
printf("fib(27)=%ld\n", res);
}
}很明显,我们将在这里启动大量的任务--因此,OpenMP版本比正常版本慢得令人吃惊,这一点也不足为奇:
$ gcc -O2 fib_slow.c
$ time ./a.out
fib(27)=196418
real 0m0.003s
user 0m0.000s
sys 0m0.000s
$ gcc -O2 fib_slow.c -fopenmp
$ time ./a.out
fib(27)=196418
real 0m0.243s
user 0m0.468s
sys 0m0.080s这个测试是在一个双核VM中运行的。注意,time报告的用户时间是实时时间的两倍,这意味着我们确实使用了第二个核心;但是我们基本上把所有时间浪费在无用的任务处理工作上,而不是实际的计算上。
很公平--这个例子的文本实际上警告我们,这仅仅是一个为教育目的而做的例子。
由于我们正在双核机器上进行测试,也许使用OpenMP的"if“结构在最顶层只启动两个线程更简单:一个计算fib(N-2)和一个fib(N-1)。
// Listing 2
#include <omp.h>
#include <stdio.h>
long fib(int val)
{
if (val < 2)
return val;
long total = 0;
{
#pragma omp task shared(total) if(val==45)
total += fib(val-1);
#pragma omp task shared(total) if(val==45)
total += fib(val-2);
#pragma omp taskwait
}
return total;
}
int main()
{
#pragma omp parallel
#pragma omp single
{
long res = fib(45);
printf("fib(45)=%ld\n", res);
}
}假设我对"if“的理解是正确的,这应该只在顶层启动两个任务(当输入为45),因此将更好地利用我们的两个核心。
我还把测试输入增加到了45,这样才能持续更长的时间。
$ gcc -O2 fib_nice.c
$ time ./a.out
fib(45)=1134903170
real 0m8.196s
user 0m8.192s
sys 0m0.000s
$ gcc -O2 fib_nice.c -fopenmp
$ time ./a.out
fib(45)=1134903170
real 1m33.237s
user 2m33.348s
sys 0m0.012s哦-哦-这绝对不是我预料的那样。
为什么?
也许我使用的是OpenMP“如果”构造错误(尽管GCC没有告诉我我这么做了)--但可以肯定的是,我会自己决定产生一项任务:
// Listing 3
#include <omp.h>
#include <stdio.h>
long fib(int val)
{
if (val < 2)
return val;
long total = 0;
{
if (val == 45) {
#pragma omp task shared(total)
total += fib(val-1);
#pragma omp task shared(total)
total += fib(val-2);
#pragma omp taskwait
} else
return fib(val-1) + fib(val-2);
}
return total;
}
int main()
{
#pragma omp parallel
#pragma omp single
{
long res = fib(45);
printf("fib(45)=%ld\n", res);
}
}别管在total上比赛的潜力--这不是重点,我只想看到我的第二个核心做些什么来改善时间。
做了吗?
$ gcc -O2 fib_nicer.c
$ time ./a.out
fib(45)=1134903170
real 0m7.974s
user 0m7.968s
sys 0m0.000s
$ gcc -O2 fib_nicer.c -fopenmp
$ time ./a.out
fib(45)=1134903170
real 0m8.773s
user 0m14.300s
sys 0m0.000s显然,自己决定自己生成一个任务大大提高了OpenMP的执行时间。不知道为什么。
但我们还是比单核执行慢.即使第一核心做fib(43)和第二核心做fib(44)应该有所帮助。
难道是OpenMP #pragma在运行时花费了我们的时间--以至于他们使整个努力失效了吗?
让我们做一个最后的实验-以最愚蠢的方式:
// Listing 4
#include <omp.h>
#include <stdio.h>
long fib_naive(int val)
{
if (val < 2)
return val;
else
return fib_naive(val-1) + fib_naive(val-2);
}
long fib(int val)
{
long total = 0;
{
#pragma omp task shared(total)
total += fib_naive(val-1);
#pragma omp task shared(total)
total += fib_naive(val-2);
#pragma omp taskwait
}
return total;
}
int main()
{
#pragma omp parallel
#pragma omp single
{
long res = fib(45);
printf("fib(45)=%ld\n", res);
}
}这基本上是手动生成两个线程。当然,这一定有效..。
$ gcc -O2 fib.c
$ time ./a.out
fib(45)=1134903170
real 0m8.738s
user 0m8.728s
sys 0m0.004s
$ gcc -O2 fib.c -fopenmp
$ time ./a.out
fib(45)=1134903170
real 0m5.446s
user 0m8.928s
sys 0m0.004s事实上,我们只花了5.4秒就完成了,而单线程执行的8.7秒。我的理论是,清单3中的if (产生顶级线程)最终花费很大,因为它是对计算中的每一个添加执行的;每个递归调用都必须经过它。
除此之外,如果你发现我所遵循的步骤有什么问题,请告诉我--因为我目前的看法是OpenMP的if是.比正常代码if慢得多。
提前感谢您的见解/建议。
发布于 2020-09-26 18:53:32
我打开了一个关于此的GCC的libgomp票 --正如您在那里所读到的,Jakub Jelinek解释说,OpenMP task中的"if(false)“并不等同于没有生成任务--事实上,与任务相关的数据结构被创建,父任务被挂起,并且这个新的子任务立即开始运行--父任务完成后立即恢复。不用说,这不仅仅是运行代码.
此外,Jakub还指出,在非OpenMP递归中,会发生ttail递归优化--即使使用了"mergeable“子句,OpenMP也不会发生这种情况。
可以说,我学到了很多-谢谢,雅库布。
https://stackoverflow.com/questions/64062046
复制相似问题