1

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

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

使用 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) 中,不使用快捷方式“parallel for”来避免创建团队的开销是否有益?
  • 我的实施的哪一部分可能导致性能下降?
  • 为什么 clock() 和 omp_get_wtime() 报告的时间如此相似,因为我预计 clock() 会比 omp_get_wtime() 长

[编辑]

  • 在 (b) 处,我在内循环中包含 OpenMP 指令的意图是外循环的迭代非常小(只有 3 次),所以我想我可以跳过它并直接进入循环 vector_4[level] 的内循环. 这种想法是否不合适(或者这会指示 OpenMP 将外循环重复 4 次,因此实际上循环内循环 12 而不是 3(例如当前线程数为 4)?

谢谢

4

3 回答 3

2

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

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

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

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

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

于 2013-07-02T15:24:18.217 回答
1

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

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

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

于 2013-07-02T15:23:51.880 回答
1

回答你的问题:

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

2) 恭喜,你的 lok-free 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 
于 2013-07-02T05:31:08.960 回答