12

我有一个可以并行化的 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's'差。

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

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

结果是最糟糕的。

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

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

4

2 回答 2

10

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

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

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

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

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

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

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

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

于 2013-03-04T16:44:11.017 回答
3

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

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

于 2013-03-09T15:14:48.210 回答