【问题标题】:Strange slowdown when using openmp使用 openmp 时出现奇怪的减速
【发布时间】:2012-07-13 19:51:53
【问题描述】:

我试图通过并行化矩阵乘法来提高相当复杂的迭代算法的性能,每次迭代都会调用该矩阵乘法。 该算法需要 500 次迭代和大约 10 秒。但是在并行化矩阵乘法之后,它会减慢到 13 秒。 但是,当我单独测试相同维度的矩阵乘法时,速度有所提高。 (我说的是 100x100 矩阵。)

最后,我关闭了算法内部的任何并行化,并在每次迭代中添加了以下代码,它完全没有任何作用,而且大概不会花费很长时间:

int j;

#pragma omp parallel for private(j)

for (int i = 0; i < 10; i++)
j = i;

同样,与没有这段代码的相同算法相比,速度会降低 30%。

因此,在主算法中使用 openmp 调用任何并行化 500 次会以某种方式减慢速度。这种行为对我来说看起来很奇怪,有人知道问题是什么吗?

主要算法被桌面应用程序调用,由VS2010,Win32 Release编译。 我在 Intel Core i3(并行化创建 4 个线程)、64 位 Windows 7 上工作。

这是一个程序的结构:

int internal_method(..)

{
...//no openmp here


 // the following code does nothing, has nothing to do with the rest of the program  and shouldn't take long,
 // but somehow adding of this code caused a 3 sec slowdown of the Huge_algorithm()
 double sum;
 #pragma omp parallel for private(sum)
 for (int i = 0; i < 10; i++)
    sum = i*i*i / (1.0 + i*i*i*i);

...//no openmp here
}


int Huge_algorithm(..)
{

 ...//no openmp here

    for (int i = 0; i < 500; i++)
    {
     .....// no openmp

     internal_method(..);

     ......//no openmp
    }

...//no openmp here
}

所以,最后一点是: 单独调用并行代码 500 次(当算法的其余部分被省略时)需要不到 0.01 秒,但是当你在一个巨大的算法中调用它 500 次时,它会导致整个算法延迟 3 秒。 而我不明白的是小的并行部分如何影响算法的其余部分?

【问题讨论】:

  • 只是为了确定,您如何衡量执行时间?我在 SO 上看到了很多与 OpenMP 和 MT 相关的问题,人们在并行程序中测量 CPU 时间而不是挂钟时间。另一件事是:进入和退出并行区域相对昂贵(即使使用现代池化 OMP 运行时)。
  • 我使用的是挂钟时间,桌面应用程序实际测量时间。进入和退出平行区域确实如此,但我有 500 次进入和退出,并且减速了 3 秒 - 这并不累加。
  • 500 个并行区域的 3 秒平均每个并行区域的开销为 6 毫秒,包括开始时的线程唤醒和结束时的同步等待。不幸的是,我无法找到有关从 Win32 线程池唤醒线程需要多长时间的信息,但 6 毫秒接近大多数 x86/x64 操作系统的调度间隔。只要时间允许,我应该做一些测量。
  • @Hristo Iliev:非常好的评论:)

标签: c++ visual-studio-2010 openmp


【解决方案1】:

对于 10 次迭代和一个简单的分配,我想与计算本身相比,OpenMP 开销太大了。这里看起来轻量级的东西实际上是管理和同步多个线程,这些线程甚至可能不是来自线程池。可能涉及一些锁定,我不知道 MSVC 在估计是否要并行化方面有多好。

尝试使用更大的循环体或更多的迭代(比如 1024*1024 迭代,只是为了初学者)。


OpenMP Magick 示例:

#pragma omp parallel for private(j)
for (int i = 0; i < 10; i++)
    j = i;

这可能由编译器大约扩展为:

const unsigned __cpu_count = __get_cpu_count();
const unsigned __j  = alloca (sizeof (unsigned) * __cpu_count);
__thread *__threads = alloca (sizeof (__thread) * __cpu_count);
for (unsigned u=0; u!=__cpu_count; ++u) {
    __init_thread (__threads+u);
    __run_thread ([u]{for (int i=u; i<10; i+=__cpu_count)
                          __j[u] = __i;}); // assume lambdas
}

for (unsigned u=0; u!=__cpu_count; ++u)
    __join (__threads+u);

__init_thread()__run_thread()__join() 是调用某些系统调用的重要函数。

如果使用线程池,您可以将第一个 alloca() 替换为 __pick_from_pool() 左右。

(请注意,名称和发出的代码都是虚构的,实际实现会有所不同)


关于您更新的问题:

您似乎以错误的粒度进行并行化。把尽可能多的工作量放在一个线程中,所以而不是

 for (...) {
     #omp parallel ...
     for (...) {} 
 }

试试

 #omp parallel ...
 for (...) {
     for (...) {} 
 }

经验法则:保持每个线程的工作负载足够大,以减少相对开销。

【讨论】:

  • MSVC 使用线程池实现 OpenMP 线程组,但开销仍然很大。
  • @Hristo Iliev:有趣的信息。你有更多关于 MSVC 实现细节的链接吗?
  • @phresnel,谢谢。并行化简单 j=i 或 sum = iii / (1.0 + iii*i) 的 10 次迭代显然不是提高性能的方法。但我的观点是,500 次调用对一个非常短的并行化周期(它什么都不做,并且与算法的其余部分无关)会导致整个程序减速 3 秒?
  • @user1523105:这就是我上次编辑的内容。你有一个大的、非并行的循环,每次迭代都会调用一个并行的内部循环。即,不是启动 x 线程一次以在 500*10 上工作,而是 500 次启动 x 线程以在 10 个实体上工作。
  • 这真的会导致 3 秒的延迟吗?因为单独调用 500 次内部并行循环(没有算法的其余部分)需要不到 0.01 秒。我无法理解这种不一致。
【解决方案2】:

也许只是 j=i 对于核心 CPU 带宽来说不是高收益。也许你应该尝试一些更有效的计算。 (例如取 i*i*i*i*i*i 并除以 i+i+i)

你是在多核 cpu 还是 gpu 上运行它?

【讨论】:

  • 多核 cpu(4 核) 我只是不明白为什么一个微不足道的 openmp 并行代码会导致如此巨大的减速。我发现使用分析器时,其他与并行化无关的方法速度变慢了
  • 你能试试矩阵乘法加上 3 次幂的计算,看看加速的区别吗?
  • @user1523105,StackOverflow 上有大量与 OpenMP 性能相关的问题。如果不向我们展示一些来自实际算法的示例代码,就无法回答这些问题。我会说您还应该使用英特尔线程检查器之类的工具运行您的代码,并查找诸如虚假共享之类的缓存问题。
  • 谢谢,Hristo。我画了一个程序的结构,如果有帮助的话。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-28
  • 2020-03-06
  • 2021-01-29
  • 1970-01-01
  • 2018-03-09
相关资源
最近更新 更多