【问题标题】:Parallel tasks get better performances with boost::thread than with ppl or OpenMP并行任务使用 boost::thread 比使用 ppl 或 OpenMP 获得更好的性能
【发布时间】:2013-03-04 16:38:00
【问题描述】:

我有一个可以并行化的 C++ 程序。我正在使用 Visual Studio 2010,32 位编译。

简而言之,程序的结构如下

#define num_iterations 64 //some number

struct result
{ 
    //some stuff
}

result best_result=initial_bad_result;

for(i=0; i<many_times; i++)
{ 
    result *results[num_iterations];


    for(j=0; j<num_iterations; j++)
    {
        some_computations(results+j);
    }

    // update best_result; 
}

由于每个some_computations() 都是独立的(读取了一些全局变量,但没有修改全局变量)我并行化了内部for-循环。

我的第一次尝试是使用 boost::thread

 thread_group group;
 for(j=0; j<num_iterations; j++)
 {
     group.create_thread(boost::bind(&some_computation, this, result+j));
 } 
 group.join_all();

结果很好,但我决定尝试更多。

我尝试了 OpenMP

 #pragma omp parallel for
 for(j=0; j<num_iterations; j++)
 {
     some_computations(results+j);
 } 

结果比boost::thread 的差。

然后我尝试了 ppl 库并使用了parallel_for()

 Concurrency::parallel_for(0,num_iterations, [=](int j) { 
     some_computations(results+j);
 })

结果是最差的。

我发现这种行为非常令人惊讶。由于 OpenMP 和 ppl 是为并行化而设计的,因此我希望得到比 boost::thread 更好的结果。我错了吗?

为什么boost::thread 会给我更好的结果?

【问题讨论】:

  • 能否请您量化“更好”,例如提供执行时间与线程数?使用boost::thread,您将创建 64 个线程。 OpenPM 使用一组工作线程,其数量默认为虚拟 CPU 的数量。 PPL 也使用了线程池,而且开销甚至比 OpenMP 更高,因为它还实现了工作平衡。
  • 我每次尝试都使用相同的数字(32 或 64),也许正如您所指出的,使用 OpenMP 和 ppl 将线程数设置为内核数可以获得更好的结果。我会试试的。
  • 现在几乎不可能回答这个问题。 some_computations 在做什么?我在某个地方可能存在争用(这可能会以不同的方式影响不同的库,例如,如果 openmp 实际上具有较低的开销,但是您对共享缓存线有很多写入,那么由此产生的缓存失效狂热实际上可能会使其变慢)?运行每个变体的并行块需要多长时间

标签: c++ openmp boost-thread ppl


【解决方案1】:

OpenMP 或 PPL 不会做悲观的事情。他们只是按照他们说的去做,但是当你尝试并行循环时,你应该考虑一些事情。

没有看到您是如何实现这些东西的,很难说真正的原因可能是什么。

此外,如果每次迭代中的操作都依赖于同一循环中的任何其他迭代,那么这将产生争用,从而减慢速度。你还没有展示你的 some_operation 函数实际上做了什么,所以很难判断是否存在数据依赖关系。

真正可以并行化的循环必须能够让每个迭代完全独立于所有其他迭代运行,并且在任何迭代中都不会访问共享内存。因此,最好将内容写入局部变量,然后在最后进行复制。

并非所有的循环都可以并行化,这在很大程度上取决于正在完成的工作类型。

例如,有利于并行化的事情是在屏幕缓冲区的每个像素上完成工作。每个像素完全独立于所有其他像素,因此,一个线程可以进行一次循环迭代并完成工作,而无需等待迭代之间循环内的共享内存或数据依赖关系。

另外,如果你有一个连续数组,这个数组可能部分在缓存行中,如果你在线程 A 中编辑元素 5,然后在线程 B 中更改元素 6,你可能会出现缓存争用,这也会减慢速度,因为它们将驻留在同一缓存行中。一种称为虚假分享的现象。

在进行循环并行化时需要考虑很多方面。

【讨论】:

  • you function some_operation 将偏移量放入数组中,并且该数组在多个线程之间共享。我不知道 PPL 或 OpenMP 是否可以使您没有写入该数组的任何保证,或者其他任何内容都写入该数组。因此我的答案不会改变。
  • 您的第一段不正确。 OpenMP 和 PPL 都不关心你对共享变量做了什么,它们的工作方式没有任何悲观或乐观的地方。两者都是命令式编程概念,这意味着如果被告知编译器会使代码并行,而不是将表达式视为提示。共享变量的正确处理只留给程序员。
【解决方案2】:

简而言之,openMP 主要是基于共享内存,任务管理和内存管理有额外的成本。 ppl 旨在处理通用数据结构和算法的通用模式,它带来了额外的复杂性成本。它们都有额外的 CPU 成本,但你简单的跌倒 boost 线程没有(boost 线程只是简单的 API 包装)。这就是为什么它们都比你的boost 版本慢。而且,由于示例计算彼此独立,没有同步,openMP 应该接近boost 版本。

它发生在简单的场景中,但是对于复杂的场景,具有复杂的数据布局和算法,它应该是上下文相关的。

【讨论】:

  • OpenMP 不是为消息传递而设计的,MPI 是传递消息的。
  • @Moss,谢谢,我混淆了 OpenMP 和 MPI。 OpenMP 是基于共享内存的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-29
  • 2021-02-05
  • 2019-04-27
  • 1970-01-01
  • 2010-09-18
  • 2017-08-29
  • 1970-01-01
相关资源
最近更新 更多