OpenMP 并行前缀和加速
Posted
技术标签:
【中文标题】OpenMP 并行前缀和加速【英文标题】:OpenMP parallel prefix sum speedup 【发布时间】:2012-10-16 15:07:34 【问题描述】:考虑下面的代码,取自here。对于这段代码,我得到以下执行时间:
time ./fibomp 40
Number of threads (OpenMP v200805): 2
finonacci(40) = 102334155
real 0m3.193s
user 0m3.180s
sys 0m0.000s
$ export OMP_NUM_THREADS=1
$ time ./fibomp 40
Number of threads (OpenMP v200805): 1
finonacci(40) = 102334155
real 0m3.224s
user 0m3.216s
sys 0m0.000s
如您所见,加速并不多,绝对不是 Ruud 在 2011 年 11 月 1 日星期二凌晨 1:41 的电子邮件中提到的 2 倍加速。我在双核机器上运行它(可能是这样吗?)。我究竟做错了什么? (顺便说一句,奖励积分,ptime
命令是什么?一些 SPARC Unix 命令?)
long comp_fib_numbers(int n)
long fnm1, fnm2, fn;
if ( n == 0 || n == 1 ) return(n);
// In case the sequence gets too short, execute the serial version
if ( n < 20 )
return(comp_fib_numbers(n-1)+comp_fib_numbers(n-2));
else
#pragma omp task shared(fnm1)
fnm1 = comp_fib_numbers(n-1);
#pragma omp task shared(fnm2)
fnm2 = comp_fib_numbers(n-2);
#pragma omp taskwait
fn = fnm1 + fnm2;
return(fn);
【问题讨论】:
我的代码没有问题。你确定你打开了所有可能的优化吗?你确定两个核心都真的启用了吗? 您的意思是您获得了 2 倍的加速?是的,两个内核都已启用,但不,我有 -O0...如果您不介意我的询问,为什么我需要为此优化? 刚用-O2试过,没有变化。 我有一个双核,每个都有超线程。我没有测量确切的加速,但它看起来相当不错。我也有-O0
的加速,但不那么明显。优化可能会影响功能的组织方式,浪费现金等等。
@JensGustedt:我想知道我做错了什么。一开始我以为 OpenMP 版本可能没有任务,所以它忽略了#pragmas,但它是 2008 年 5 月的版本,它是 3.0,因此有任务。我没有HT。
【参考方案1】:
首先,为了确定,既然您声明 htop
表明正在使用单个内核,请确保您已在编译器中启用 OpenMP 支持。这样做的选项是 -fopenmp
用于 GCC,-xopenmp
用于 Sun/Oracle 编译器,-openmp
用于 Intel 编译器。
其次,n = 20
对于并行实现来说可能太低了。一个无耻的插件 - 请参阅我的同事几个月前举办的 OpenMP 研讨会上的 this course material。从幻灯片 20 开始,这里讨论了几个带有任务的并行版本。
第三,ptime
是一个 Solaris 命令,并非特定于 SPARC,因为它在 x86 版本中也可用。许多与进程相关的 Solaris 命令的名称中都有 p
前缀。请注意,在您的情况下,time
更有可能是 Bash 提供的内置实现,而不是独立的二进制文件。
第四,可能是您问题的真正答案 - 您的代码中缺少 parallel
区域,因此任务指令根本不起作用 :) 您应该如下重写代码:
long comp_fib_numbers(int n)
long fnm1, fnm2, fn;
if ( n == 0 || n == 1 ) return(n);
// In case the sequence gets too short, execute the serial version
if ( n < 20 )
return(comp_fib_numbers(n-1)+comp_fib_numbers(n-2));
else
#pragma omp parallel // <--- You are missing this one parallel region
#pragma omp single
#pragma omp task shared(fnm1)
fnm1 = comp_fib_numbers(n-1);
#pragma omp task shared(fnm2)
fnm2 = comp_fib_numbers(n-2);
#pragma omp taskwait
fn = fnm1 + fnm2;
return(fn);
您可以通过使用if
子句切换并行区域使代码更加简洁:
long comp_fib_numbers(int n)
long fnm1, fnm2, fn;
if ( n == 0 || n == 1 ) return(n);
#pragma omp parallel if(n >= 20)
#pragma omp single
#pragma omp task shared(fnm1)
fnm1 = comp_fib_numbers(n-1);
#pragma omp task shared(fnm2)
fnm2 = comp_fib_numbers(n-2);
#pragma omp taskwait
fn = fnm1 + fnm2;
return(fn);
如果n
恰好小于 20,则并行区域将执行单线程。由于并行区域通常在单独的函数中提取,因此仍然会有额外的函数调用,除非编译器选择生成重复代码。这就是为什么建议将串行实现提取到自己的函数中:
long comp_fib_numbers_serial(int n)
if ( n == 0 || n == 1 ) return(n);
return (comp_fib_numbers_serial(n-1) + comp_fib_numbers_serial(n-2));
long comp_fib_numbers(int n)
long fnm1, fnm2, fn;
if ( n < 20 ) return comp_fib_numbers_serial(n);
#pragma omp parallel
#pragma omp single
#pragma omp task shared(fnm1)
fnm1 = comp_fib_numbers(n-1);
#pragma omp task shared(fnm2)
fnm2 = comp_fib_numbers(n-2);
#pragma omp taskwait
fn = fnm1 + fnm2;
return(fn);
编辑: 现在我已经查看了您链接到的代码,我可以看到对 comp_fib_numbers
的调用嵌入到 parallel
区域中。因此,如果您的代码中已经有一个,请忽略我对缺少的 parallel
区域的评论。为了完整起见,我将把它留在这里。尝试调整并行版本和串行版本之间发生切换的值。在现代处理器上,它可能会很高,而且您看到的示例已经很老了。此外,通过将环境变量OMP_DYNAMIC
设置为false
(或FALSE
)或在并行区域之前的某个位置调用omp_set_dynamic(0);
,确保不使用动态团队。
您尚未说明您的编译器是什么,但请注意,自 4.4 版起,GCC 支持 OpenMP 3.0,自 11.0 版起由英特尔编译器支持,自 I_dont_know 版起由 Sun/Oracle 编译器支持,并且 Visual C 根本不支持/C++ 编译器。
在四路 Intel Xeon X7350 系统(带有 FSB 的旧前 Nehalem 系统)上观察到的加速
$ time OMP_NUM_THREADS=1 ./fib.x 40
finonacci(40) = 102334155
OMP_NUM_THREADS=1 ./fib.x 40 1.86s user 0.00s system 99% cpu 1.866 total
$ time OMP_NUM_THREADS=2 ./fib.x 40
finonacci(40) = 102334155
OMP_NUM_THREADS=2 ./fib.x 40 1.96s user 0.00s system 169% cpu 1.161 total
截止设置为25
(似乎是 X7350 的最佳值):
$ time OMP_NUM_THREADS=2 ./fib.x 40
finonacci(40) = 102334155
OMP_NUM_THREADS=2 ./fib.x 40 1.95s user 0.00s system 169% cpu 1.153 total
将截止设置为25
和串行实现的单独函数:
$ time OMP_NUM_THREADS=2 ./fib.x 40
finonacci(40) = 102334155
OMP_NUM_THREADS=2 ./fib.x 40 1.52s user 0.00s system 171% cpu 0.889 total
看看用户时间如何减少了大约 400 毫秒。这是因为消除了开销。
这些是使用您链接到的网站中的代码测量的。使用的编译器是 64 位 Scientific Linux 6.2 系统上的 GCC 4.4.6。
【讨论】:
Hristo,哇,很好的答案,谢谢。非常令人印象深刻。我尝试输入#pragma omp parallel
指令,但它告诉我 error: invalid branch to/from an OpenMP structured block
... 再次感谢,这正是我开始研究解决方案所需要的。
我认为在上面的代码 sn-p 中pragma omp task
应该包含在single
构造中,否则所有经过的线程都会生成一个新任务。
@DervinThunk,我的错误 - 你不能在 parallel
块内使用 return
。请坚持上层函数中具有并行区域的原始代码或将return语句移到区域之外。
@Massimiliano,代码是正确的。任务排队,然后由空闲线程在某些同步点执行,例如 taskwait
构造。
@HristoIliev 我相信您的代码的行为与预期的不同:您的代码会生成许多每个线程恒定的任务。 Here 带有孤立任务指令的函数在 single nowait
构造中调用。以上是关于OpenMP 并行前缀和加速的主要内容,如果未能解决你的问题,请参考以下文章