C ++ OpenMP比使用默认线程计数的串行慢

时间:2013-07-02 04:33:00

标签: c++ openmp

我尝试使用OpenMP来并行我的程序的一些for循环,但未能显着提高速度(观察到实际降级)。我的目标机器将有4-6个核心,我目前依赖OpenMP运行时来获取线程数,所以我还没有尝试任何线程数组合。

  • 目标/开发平台:Windows 64bits
  • 使用MinGW64 4.7.2(rubenvb build)

使用OpenMP输出示例

Thread count: 4
Dynamic :0
OMP_GET_NUM_PROCS: 4
OMP_IN_PARALLEL: 1
5.612 // <- returned by omp_get_wtime()
5.627 (sec) // <- returned by clock()
Wall time elapsed: 5.62703

没有OpenMP的示例输出

2.415 (sec) // <- returned by clock()
Wall time elapsed: 2.415

我如何衡量时间

struct timeval start, end;
gettimeofday(&start, NULL);

#ifdef _OPENMP
    double t1 = (double) clock();
    double wt = omp_get_wtime();
    sim->resetEnvironment(run);
    tout << omp_get_wtime() - wt << std::endl;
    timeEnd(tout, t1);
#else
    double = (double) clock();
    sim->resetEnvironment(run);
    timeEnd(tout, t1);
#endif

gettimeofday(&end, NULL);
tout << "Wall time elapsed: "
     << ((end.tv_sec - start.tv_sec) * 1000000u + (end.tv_usec - start.tv_usec)) / 1.e6
     << std::endl;

代码

void Simulator::resetEnvironment(int run)
{
    #pragma omp parallel
    {
        // (a)
        #pragma omp for schedule(dynamic)
        for (size_t i = 0; i < vector_1.size(); i++) // size ~ 20
            reset(vector_1[i]);
        #pragma omp for schedule(dynamic)
        for (size_t i = 0; i < vector_2.size(); i++) // size ~ 2.3M
            reset(vector_2[i]);
        #pragma omp for schedule(dynamic)
        for (size_t i = 0; i < vector_3.size(); i++) // size ~ 0.3M
            reset(vector_3[i]);
        for (int level = 0; level < level_count; level++) // (b) level = 3
        {
            #pragma omp for schedule(dynamic)
            for (size_t i = 0; i < vector_4[level].size(); i++) // size ~500 - 1K
                reset(vector_4[level][i]);
        }

        #pragma omp for schedule(dynamic)
        for (long i = 0; i < populationSize; i++) // size ~7M
            resetAgent(agents[i]);
    } // end #parallel
} // end: Simulator::resetEnvironment()

随机性的 在reset()函数调用内部,我使用RNG为后续任务播种一些代理。 下面是我的RNG实现,因为我看到建议每个线程使用一个RNG来保证线程安全。

class RNG {
public:
typedef std::mt19937 Engine;

RNG()
    : real_uni_dist_(0.0, 1.0)
#ifdef _OPENMP
    , engines()
#endif
    {
#ifdef _OPENMP
        int threads = std::max(1, omp_get_max_threads());
        for (int seed = 0; seed < threads; ++seed)
            engines.push_back(Engine(seed));
#else
        engine_.seed(time(NULL));
#endif
    } // end_ctor(RNG)

    /** @return next possible value of the uniformed distribution */
    double operator()()
    {
    #ifdef _OPENMP
         return real_uni_dist_(engines[omp_get_thread_num()]);
    #else
         return real_uni_dist_(engine_);
    #endif
    }

private:
    std::uniform_real_distribution<double> real_uni_dist_;
#ifdef _OPENMP
    std::vector<Engine> engines;
#else
    std::mt19937 engine_;
#endif
}; // end_class(RNG)

问题:

  • 在(a),不使用快捷方式&#39;并行为了避免创建团队的开销?
  • 我实施的哪一部分可能是性能下降的原因?
  • 为什么clock()和omp_get_wtime()报告的时间非常相似,因为我预期clock()会比omp_get_wtime()
  • 更长

[编辑]

  • 在(b)中,我在内循环中包含OpenMP指令的意图是外循环的迭代是如此之小(只有3)所以我想我可以跳过它并直接转到循环vector_4的内部循环[水平]。这个想法是不合适的(或者这会指示OpenMP重复外循环4,因此实际上循环内循环12而不是3(假设当前线程数是4)?

由于

3 个答案:

答案 0 :(得分:2)

如果测量的挂钟时间(由omp_get_wtime()报告)接近总CPU时间(由clock()报告),这可能意味着几个不同的事情:

  • 代码运行单线程,但总CPU时间将低于挂钟时间;
  • 存在非常高的同步和缓存一致性开销,与线程实际工作相比,它是巨大的。

您的案例是第二个,原因是您使用schedule(dynamic)。动态调度应仅用于每次迭代可能花费不同时间的情况。如果这些迭代静态地分布在线程中,则可能发生工作不平衡。 schedule(dynamic)通过将每个任务(在您的情况下,每次循环迭代)提供给下一个线程来完成其工作并变为空闲来处理这个问题。在同步线程和簿记工作项的分配方面存在一定的开销,因此只应在每个线程的工作量与开销相比较大时使用。 OpenMP允许您将更多迭代分组到迭代块中,并且此数字指定为schedule(dynamic,100) - 这将使每个线程在请求​​新的迭代之前执行100次连续迭代的块(或块)。 动态调度的默认块大小为1 ,即由单独线程处理的每个向量元素。我不知道在reset()中进行了多少处理,vector_*中有哪些元素,但考虑到串行运行时间,它根本不是很多。

另一个减速源是使用动态调度时数据局部性的丢失。根据这些向量的元素类型,通过不同线程处理相邻元素会导致 false sharing 。这意味着,例如, vector_1[i]位于与vector_1的其他元素相同的缓存行中,例如vector_1[i-1]vector_1[i+1]。当线程1修改vector_1[i]时,缓存行将在所有其他处理相邻元素的核心中重新加载。如果仅写入vector_1[],则编译器可以足够智能以生成非临时存储(绕过缓存)但它仅适用于向量存储并且每个核心一次执行单次迭代意味着没有向量化一点都不通过切换到静态调度,或者如果reset()确实需要不同的时间,可以通过在schedule(dynamic)子句中设置合理的块大小来改进数据局部性。最好的块大小通常取决于处理器,通常必须调整它以获得最佳性能。

因此,我强烈建议您首先将所有schedule(dynamic)替换为schedule(static),然后再尝试进行优化,从而切换到静态计划。您不必在静态情况下指定块大小,因为默认值只是迭代总数除以线程数,即每个线程将获得一个连续的迭代块。

答案 1 :(得分:1)

回答你的问题:

1)在a)中使用“parallel”关键字是准确的

2)恭喜,你的无法获得的PRNG看起来很好

3)错误可能来自您在内部循环中使用的所有OpenMP pragma。在顶层平行并避免细粒度和内环平行

4)在下面的代码中,我在'omp for'上使用'nowait',我将omp指令置于vector_4进程中的循环外,并在最后添加一个屏障来加入所有线程并且我们之前产生的所有工作结束了!

    // pseudo code

    #pragma omp for schedule(dynamic) nowait 
    for (size_t i = 0; i < vector_1.size(); i++) // size ~ 20
        reset(vector_1[i]);
    #pragma omp for schedule(dynamic) nowait 
    for (size_t i = 0; i < vector_2.size(); i++) // size ~ 2.3M
        reset(vector_2[i]);
    #pragma omp for schedule(dynamic) nowait 
    for (size_t i = 0; i < vector_3.size(); i++) // size ~ 0.3M
        reset(vector_3[i]);

    #pragma omp for schedule(dynamic) nowait 
    for (int level = 0; level < level_count; level++)
    {
        for (size_t i = 0; i < vector_4[level].size(); i++) // size ~500 - 1K
            reset(vector_4[level][i]);
    }

    #pragma omp for schedule(dynamic) nowait 
    for (long i = 0; i < populationSize; i++) // size ~7M
        resetAgent(agents[i]);

    #pragma omp barrier 

答案 2 :(得分:1)

如果有用的处理时间小于线程产生的开销,则单线程程序将比多线程程序运行得更快。

通过实现null函数然后确定它是否是更好的解决方案来确定开销是个好主意。

从性能的角度来看,只有当有用的处理时间明显高于线程产生的开销并且有可用于运行线程的真实cpu时,线程才有用。