使用 OpenMP 进行并行加速
Posted
技术标签:
【中文标题】使用 OpenMP 进行并行加速【英文标题】:Parallel speedup with OpenMP 【发布时间】:2011-10-28 05:30:08 【问题描述】:我有两种测量指标的场景,例如计算时间和并行加速(sequential_time/parallel_time)。
场景 1:
顺序时间测量:
startTime=omp_get_wtime();
for loop computation
endTime=omp_get_wtime();
seq_time = endTime-startTime;
并行时间测量:
startTime = omp_get_wtime();
for loop computation (#pragma omp parallel for reduction (+:pi) private (i)
for (blah blah)
computation;
endTime=omp_get_wtime();
paralleltime = endTime-startTime;
speedup = seq_time/paralleltime;
场景 2:
顺序时间测量:
for loop
startTime=omp_get_wtime();
computation;
endTime=omp_get_wtime();
seq_time += endTime-startTime;
并行时间测量:
for loop computation (#pragma omp parallel for reduction (+:pi, paralleltime) private (i,startTime,endTime)
for (blah blah)
startTime=omp_get_wtime();
computation;
endTime=omp_get_wtime();
paralleltime = endTime-startTime;
speedup = seq_time/paralleltime;
我知道场景 2 不是最好的生产代码,但我认为它通过忽略 openmp 生成和管理(线程上下文切换)多个线程所涉及的开销来衡量实际的理论性能。所以它会给我们一个线性加速。但是场景 1 考虑了产生和管理线程所涉及的开销。
我的疑问是: 在场景 1 中,我得到了一个从线性开始的加速,但随着我们转向更多的迭代次数而逐渐减少。在场景 2 中,无论迭代次数如何,我都能获得完全的线性加速。有人告诉我,实际上,场景 1 将给我一个线性加速,而不管迭代次数如何。但我认为这不会因为线程管理导致的高过载。有人可以向我解释为什么我错了吗?
谢谢!对于这篇相当长的帖子感到抱歉。
【问题讨论】:
方案 2 中的并行时间测量不正确,因为在每次迭代中,您都会在同一线程上重新编写上一次迭代获得的结果。如果你把它改成paralleltime += endTime-startTime;
(注意+=),你将总结所有迭代的时间,这将与seq_time
基本相同。
这就是为什么我在 pi 和 paralleltime 上执行 reduce 操作 #pragma omp parallel for reduction (+:pi, paralleltime) private (i,startTime,endTime)
减少paralleltime
不会使您的程序正确。您可以查看 OpenMP 规范,特别是第 2.9.3.6 节以了解详细信息。简而言之,归约变量的每个私有副本都会累积由同一线程执行的几次循环迭代的结果。为了说明,让我们假设第 1 次和第 2 次循环迭代由同一个线程执行;然后第 2 次迭代将其时间写入第 1 次迭代的时间,而第 1 次迭代将丢失。
谢谢阿列克谢!我不知道减少是以这种方式起作用的..
【参考方案1】:
在很多情况下,场景 2 也不会给您带来线性加速——线程之间的错误共享(或者,就此而言,共享变量的真实共享会被修改)、内存带宽争用等。加速通常是真实的,而不是测量工件。
更一般地说,一旦您将计时器放入 for 循环中,您就会考虑比使用此类计时器真正适合测量的更细粒度的计时信息。出于各种原因,您可能很希望能够将线程管理开销与正在完成的实际工作分离开来,但在这里您试图通过向omp_get_wtime()
插入 N 个额外的函数调用以及算术来做到这一点和归约操作,所有这些都将产生不可忽略的开销。
如果您真的想准确计时在computation;
行中花费了多少时间,您真的想使用采样之类的东西而不是手动检测(我们稍微讨论一下here 的区别)。使用gprof 或scalasca 或openspeedshop(所有免费软件)或英特尔的VTune 或其他东西(商业包)将为您提供关于在该行上花费了多少时间的信息 - 通常甚至是线程 -低得多的开销。
【讨论】:
【参考方案2】:首先,根据加速比的定义,你应该使用场景1,其中包括并行开销。
在场景 2 中,您在paralleltime
的测量中输入了错误的代码。为了满足您在场景2中的目标,您需要通过分配int paralleltime[NUM_THREADS]
并通过omp_get_thread_num()
访问它们来拥有每个线程paralleltime
(注意此代码将具有错误共享,因此您最好分配64 -byte 结构与填充)。然后,测量每个线程的计算时间,最后取最长的一个来计算不同类型的加速(我会说一种并行性)。
不,即使场景 2,您也可能会看到亚线性加速,甚至可以获得超线性加速。潜在的原因(即不包括并行开销)是:
-
负载不平衡:
compuation
中的工作负载长度在迭代时不同。这将是低加速的最常见原因(但是,您说负载不平衡不是这种情况)。
同步成本:如果有任何类型的同步(例如,互斥锁、事件、屏障),您可能需要等待/阻塞时间。
缓存和内存成本:当computation
需要大带宽和高工作集集时,并行代码可能会受到带宽限制(尽管在现实中很少见)和缓存冲突的影响。此外,虚假分享也是一个重要原因,但很容易避免。还可以观察到超线性效应,因为使用多核可能有更多的缓存(即私有 L1/L2 缓存)。
在场景一中,会包含并行库的开销:
-
分叉/加入线程:尽管大多数并行库实现不会在每个并行结构上创建/终止物理线程。
分派/加入逻辑任务:即使已经创建了物理线程,也需要将逻辑任务分派给每个线程(一般是M任务到N线程),并且还要进行某种join最后的操作(例如,隐式屏障)。
调度开销:对于静态调度(如您的代码所示,它使用 OpenMP 的静态调度),开销是最小的。当工作负载足够时(比如 0.1 秒),您可以放心地忽略开销。但是,动态调度(例如 TBB 中的工作窃取)会产生一些开销,但一旦您的工作负载足够,这并不重要。
我不认为您的代码(1 级静态调度并行循环)由于线程管理而具有很高的并行开销,除非此代码每秒调用数百万次。所以,可能是我在上面提到的其他原因。
请记住,决定加速的因素有很多;从固有的并行性(=负载不平衡和同步)到并行库的开销(例如调度开销)。
【讨论】:
你好敏江。注册。并行时间计算,我也在场景 2 中减少并行时间。所以我得到的并行时间是正确的,对吧? 顺便说一句,我喜欢你的回答。给出了很好的详细解释。但不知何故,我觉得我必须选择另一个。 :)【参考方案3】:您要准确测量什么?由于并行性导致的开销是实际执行时间的一部分,因此恕我直言,场景 1 更好。
此外,根据您的 OpenMP 指令,您正在对某些数组进行缩减。在方案 1 中,您正在考虑这一点。在场景 2 中,您不是。所以基本上,你测量的东西比场景 1 中的要少。这可能也会对你的测量产生一些影响。
否则,Jonathan Dursi 的答案非常好。
【讨论】:
【参考方案4】:对于如何在线程之间分配工作,OpenMP 有几个选项。这会影响测量方法 1 的线性度。测量方法 2 似乎没有用。你想解决什么问题?如果您想了解单线程性能,请运行单线程。如果您想要并行性能,则需要包括开销。
【讨论】:
以上是关于使用 OpenMP 进行并行加速的主要内容,如果未能解决你的问题,请参考以下文章
在矩阵乘法中使用 C++2011 线程而不是 OpenMP 时出现异常加速