【问题标题】:Is it possible to run a Minimax search with Alpha-Beta Pruning in parallel with OpenMP?是否可以使用 Alpha-Beta 修剪与 OpenMP 并行运行 Minimax 搜索?
【发布时间】:2014-11-05 01:13:21
【问题描述】:

通过基本的 Minimax 搜索,使用 OMP For 在多个线程之间拆分工作似乎很容易。例如 -

#pragma omp parallel for
for (each child node)
{
    val = minimax(child, depth - 1, FALSE);
    bestValue = max(bestValue, val);
}

但是,至少在我的理解中,Alpha-Beta 修剪似乎是不可能的。

#pragma omp parallel for
for (each child node)
{
   α = max(α, alphabeta(child, depth - 1, α, β, FALSE));
   if (β ≤ α)
       break;
}

在 OpenMP 中,如果要使循环并行,则要求 For 循环只能有一个入口/出口点。然而,Alpha-Beta 剪枝打破了这个规则,因为只要需要完成剪枝,就有可能跳出循环(在上面的伪代码中,这将在 β 小于或等于 α 时发生)。

所以我的问题是,有没有办法解决 OpenMP 的这种限制?我想使用 OpenMP 并行运行我的 Alpha-Beta 搜索,但目前这个限制让我很困惑。

【问题讨论】:

  • 这是可能的,但您必须使用一些更底层的 OpenMP 构造,使用线程 ID 和同步。
  • 啊,好吧。您特别指的是 OMP 的哪些元素?只是线程 ID 和同步?我很好奇如何解决这个问题,或者网上是否有任何示例可以为我指明正确的方向。到目前为止,我的搜索没有产生任何有希望的结果。
  • 我稍后会写一个简单的例子来说明我将如何做到这一点,但有两个棘手的地方。一个是您的 alphabeta 函数使用上一次迭代中的 alpha 值,这是您需要摆脱的依赖项。其次,第一个完成的线程不一定是具有smalles子节点索引的线程。
  • 好的,所以这似乎是一个树搜索?您必须提供有关如何遍历子节点的更多详细信息,因为我猜这不是一些简单的 for 循环?使用 OpenMP 浏览链表也比较棘手,但您可能会使用任务来处理它。
  • 如果您遍历树,您可以为节点的子节点生成任务,该节点将递归地继续遍历。只需搜索“tree traversal openmp tasks”,您就会找到一些材料。正弦任务将继续遍历或仅返回(如果 b

标签: parallel-processing openmp alpha-beta-pruning


【解决方案1】:

作为第一个想法,您可以并行计算根移动(例如,使用 #pragma omp parallel for schedule(dynamic, 1),以便每个线程都有自己的根移动,然后像任务池一样,选择没有线程触及的下一个。因为您必须计算每个根移动,所以线程必须离开循环的中断没有问题。如果线程准备好它的根移动,您可以更新“最佳值”并将其用于下一个根移动。它必须是一个共享值,并且您必须使用 #pragma omp critical 来保护它。

如果您只有 2-4 个工作线程,这是一种令人满意的方法。但是当一个位置只有几个根移动或者你有一个非常好的移动排序功能时,它有缺点。如果首先计算最佳移动,则在顺序情况下,由于截止,其他移动将被计算得非常快。因此,如果其他线程在可能的“最佳移动”的值已知之前并行搜索其他根移动,那么这是一种计算能力的浪费。有可能如此有效地排序移动,以至于在超过 99% 的情况下,最佳移动将在第一时间计算出来。这适用于称为“历史表”、“杀手移动”和“空移动修剪”的助手。

作为最先进的并行化,您可以使用 Young Brothers Wait Concept (YBWC)。在那里,就像在 Principal Variation Splitting (PVS) 中一样,在每个节点中,第一步是计算顺序的 (!),并且只有在没有截止的情况下,替代移动 (兄弟) 才会并行计算。兄弟们已经计算出第一个节点的值,可以快速截断,只尝试击败它。几乎前 10 名中的每个开源引擎都使用它自己的变体,但它不容易实现,因为您可以在不同深度的每个节点中生成任务,因此使用标准 OpenMP 构造来制定它并不容易。新的 OpenMP 3.1 tasks-construct 可以提供帮助,但我不确定。作为第一个去的地方,您可以访问https://www.chessprogramming.org/Parallel_Search 阅读该主题。

【讨论】:

    【解决方案2】:

    使用任务构造,您可以遍历不规则结构。在搜索过程中形成的树是应用任务的示例。

    一个典型的例子是斐波那契或解数独:

    #include <iostream>
    #include <omp.h>
    #include <cstdlib>
    using namespace std;
    
    unsigned long fib(unsigned long n) {
        unsigned long a = 0;
        unsigned long b = 0;
    
        if (n == 0) return n;
        if (n == 1) return n;
        #pragma omp task shared(a)
        a = fib(n-1);
    
        #pragma omp task shared(b)
        b = fib(n-2);
    
        #pragma omp taskwait
        return a+b;
    }
    
    
    int main(int argc, char** argv) {
        unsigned long result = 0;
        unsigned long N = atol(argv[1]);
        if (N < 0) {
            cerr << "N is a negative number" << endl;
            return -1;
        }
        double start = omp_get_wtime();
        #pragma omp parallel
        {
            #pragma omp single
            result = fib(N);
        }
        double elapsed = omp_get_wtime() - start;
        cout << result << endl;
        cout << "Elapsed time: " << elapsed << endl;
    
        return 0;
    }
    

    这个示例代码对于任务添加的开销来说太简单了,但是您可以使用小数字进行测试(fib(30) 或 fib(35) 在我的机器上使用 2 个线程持续 25 秒)并查看递归是如何工作的。 我认为添加上一张海报所说的内容,您可以探索这条路。

    另一方面,根据另一张海报,alphabeta 是一种串行算法,因此在不进行重大更改的情况下让它并行工作将非常棘手。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多