【问题标题】:Recursive parallel function not using all cores递归并行函数不使用所有内核
【发布时间】:2013-11-22 10:07:53
【问题描述】:

我最近实现了一个递归 negamax 算法,我使用 OpenMP 对其进行了并行化。

有趣的是:

#pragma omp parallel for
for (int i = 0; i < (int) pos.size(); i++)
{
    int val = -negamax(pos[i].first, -player, depth - 1).first;

    #pragma omp critical
    if (val >= best)
    {
        best = val;
        move = pos[i].second;
    }
}

在我的 Intel Core i7(4 个物理内核和超线程)上,我观察到一些非常奇怪的现象:在运行算法时,它并没有使用所有 8 个可用线程(逻辑内核),而只使用了 4 个。

谁能解释为什么会这样?我理解算法无法很好扩展的原因,但为什么它不使用所有可用的内核?

编辑:我将 thread 更改为 core,因为它可以更好地表达我的问题。

【问题讨论】:

  • 您的临界区似乎太小了。尝试同时更改best 的两个线程可能会输入错误的数字。
  • @Leeor,实际上该算法产生了正确的结果。我的问题是为什么不所有线程都被激活。
  • 我知道这不能回答你的问题,它只是一个评论。每次您尝试时它都会返回正确的结果这一事实并不意味着它总是会这样做。
  • @Leeor,为什么我的临界区太小了?我认为只涵盖了更改共享数据的代码。
  • @gg.kaspersky,你的测试也应该被覆盖,即if (val &gt;= best)在你的关键部分你修改best,但是测试在外面..

标签: c++ performance openmp


【解决方案1】:

首先,检查您是否有足够的迭代次数,pos.size()。显然这应该是一个足够的数字。


递归并行是一种有趣的模式,但它可能不适用于 OpenMP,除非您使用的是 OpenMP 3.0 的 task、Cilk 或 TBB。有几点需要考虑:

(1) 为了使用递归并行,您大多需要显式调用omp_set_nested(1)。 AFAIK,OpenMP 的大多数实现都不会递归生成 parallel for,因为它最终可能会创建数千个物理线程,只会让你的操作系统爆炸。

在 OpenMP 3.0 的 task 之前,OpenMP 具有逻辑并行任务到物理任务的一对一映射。因此,在这种递归并行性中它不会很好地工作。尝试一下,但即使创建了数千个线程也不要感到惊讶!

(2)如果你真的想在传统的OpenMP中使用递归并行,你需要实现控制活动线程数的代码:

if (get_total_thread_num() > TOO_MANY_THREADS) {
  // Do not use OpenMP
  ...
} else {
#pragma omp parallel for
  ...
}

(3) 您可以考虑使用 OpenMP 3.0 的 task。在您的代码中,由于递归,可能会有大量并发任务。为了在并行机器上高效工作,必须有一种有效的将这些逻辑并发任务映射到物理线程(或逻辑处理器、内核)的算法。 OpenMP 中的原始递归并行性将创建实际的物理线程。 OpenMP 3.0 的 task 没有。

你可以参考我之前关于递归并行的回答:C OpenMP parallel quickSort

(4) Intel 的 Cilk Plus 和 TBB 支持完全嵌套和递归并行。在我的小测试程序中,性能远远优于 OpenMP 3.0。但是,那是3年前的事了。您应该检查最新的 OpenMP 实现。


我对@9​​87654331@和minimax不是很了解。但是,我的直觉说,使用递归模式和锁不太可能加快速度。一个简单的谷歌搜索给我:http://supertech.csail.mit.edu/papers/dimacs94.pdf

“但是 negamax 不是一种有效的串行搜索算法,因此,它 并行化它没有什么意义。”

【讨论】:

  • 关于 negamax 的那句话并不意味着它可以并行化(实际上可以并行化,并且可以很好地扩展),但还有其他更快的算法(例如带有 alpha-beta 修剪的 negamax) ,一个人应该关注和并行化。
  • 但你是对的,我调查了 pos.size() ,有时向量中的元素少于 8 个,有时更多。我人为地添加了重复的位置,以及所有使用的核心。谢谢。而且我也会去openmp看看task
  • 我准确地发现了问题:只有 4 个初始动作,所以起初 negamax call 产生了 4 个线程。之后,在递归中,OpenMP 不再产生任何线程。我这样做是为了让最初只有 3/5 的移动是可能的,并且只使用了 3/5 的核心。
  • 很高兴听到这个消息。在打开omp_set_nested 时也让我知道。我想知道会有加速。
  • 启用 omp_nested 将启用递归在每个级别上创建新线程。我的程序崩溃了,达到了最大线程数(我认为有数百个)。
【解决方案2】:

除了尽可能多的可用线程外,最佳并行度级别还有一些其他注意事项。例如,操作系统用于将单个进程的所有线程调度到单个处理器以优化缓存性能(除非程序员明确更改)。

我猜 OpenMP 在执行此类代码时会考虑类似的因素,您不能总是假设执行了最大线程数/

【讨论】:

    【解决方案3】:

    什么意思是所有 8 个可用线程?像这样的 CPU 可能可以运行 100 多个线程!您可能认为具有超线程的 4 个内核等同于 8 个线程,但您的 OpenMP 安装可能并非如此。

    检查:

    • 是否已创建并设置了环境变量OMP_NUM_THREADS?如果它设置为 4,那么您的答案就是,您的 OpenMP 环境配置为最多仅启动 4 个线程。
    • 如果尚未设置该环境变量,请调查 OpenMP 例程 omp_get_num_threads()omp_set_num_threads() 的使用和影响。如果已设置环境变量,则 omp_set_num_threads() 将在运行时覆盖它。
    • 8 个超线程是否优于 4 个真实线程。
    • 是否超额订阅,例如OMP_NUM_THREADS 设置为 16,除了破坏性能之外还有其他作用。

    【讨论】:

    • 对不起,我使用的术语线程表示逻辑核心。我认为使用#pragma omp parallel 应该默认为(逻辑)核心的数量,在我的情况下为8。我实现并测试了相同的算法,但以迭代形式,在相同的环境中,在这种情况下使用所有核心。是的,8 个超线程优于 4 个真实线程。
    猜你喜欢
    • 2017-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多