下面开始介绍一些在Alpha-Beta算法中引入并行化的方法和算法.
6.1 并行求值(Parallel Evaluation)
游戏的博弈程序经常要在搜索深度和叶结点的求值复杂度之间进行平衡.一些博弈程序,使用简化的估值函数,以获得更深的搜索深度.但是花费在对叶结点的求值中的时间仍然占搜索时间的很大一部分.一个在博弈树搜索中应用并行性的思想[6]就是将求职函数设计得较为复杂,并将它划分到多个处理器中并行计算,然后综合各部分结果,得到最终的估值.
6.2 并行吸出(Parallel Aspiration)
并行吸出(Parallel Aspiration)或者窗口分裂(Window Spittting)搜索[13]基于这样的思想:如果将搜索窗口设置为一个小的区间,如果最大最小值处于这个小的区间中,那么就可以在搜索较少的叶结点的情况下得到最大最小值.
在并行吸出搜索中,初始搜索窗口[-∞, +∞]被划分成p个不相交的区间.其中p为处理器的个数.每个处理器以分配给它们的区间作为搜索窗口,使用Alpha-Beta算法并行地搜索同一棵博弈树.由于这些搜索窗口覆盖了初始窗口[-∞, +∞],所以总有一个处理器可以搜索到正确的最大最小值.当这个正确的最大最小值被一个处理器搜索到之后,所有其他的处理器就立即停止搜索.
实验表明[6],在一些情况下,当处理器个数p较少时(例如,p为2或3),这种算法可以获得超过p倍的加速,但是,算法能获得的最大加速却非常有限,并且与处理器个数的多少无关.
6.3 Tree Splitting 算法
在Tree Splitting 算法中,处理器同过通信线(communicationline)以树形结构组织起来,形成一棵处理器树(processor tree).处理器树的结点对应一个处理器,结点之间的连线对应于通信线.处理器树中的每个内部处结点都有若干子结点,这些子结点均为父结点的子处理器(child processor).没有子结点的处理器称为叶处理器(leaf processor),否则称为主处理器(master).叶处理器执行串行的Alpha-Beta算法,也就是从算法(slavealgorithm),而主处理器均执行主算法(master algorithm).
在Tree Splitting 算法开始后,博弈树的根结点由给处理器树的根结点处理.该根处理器产生博弈树的根结点的所有移动,并将这些子结点交给它的子处理器处理.父处理器总是把自身负责的结点的子结点交给其子处理器处理,如此继续,直到一个结点的搜索被分配给处理器树中的叶结点.叶处理器执行串行Alpha-Beta算法,并将结果提交给其父处理器.内部结点接收其子处理器提交的博弈值,并判断是否能据此改变搜索窗口的alpha值,如果可以,它将中断所有子处理器的搜索,并强迫所有子孙处理器更新搜索窗口.当内部结点接收到所有子处理器提交的博弈值之后,就计算最佳的博弈值并提交给父处理器.最后,就可以在根处理器里得到博弈树的最大最小值.
一个Tree Splitting 算法的伪代码如下所示[6]:
TreeSplit(node, alpha, beta)
1: if(I am a leaf processor) then
2: return(AlphaBeta(node, alpha, beta))
3: fori ← 1 to node.branch.length par-do
4: while(a slave j is idle) do
5: new_node ← Traverse(node,node.branch[i])
6: value[i] ← -j.Treesplit(new_node,-beta, -alpha)
7: critical begin
8: if value[i] > alpha then
9: alpha ← value[i]
10: critical end
11: if alpha ≥ beta then
12: Terminate()
13: return alpha
14: returnalpha
Update(depth, side, bound)
1: if(side > 0) then
2: alpha[depth] ← Max(alpha[depth],bound)
3: else
4: beta[depth] ← Min(beta[depth],bound)
5: ifdepth > 0 then
6: Update(depth-1, -side,-bound)
上述伪代码中,Update()函数就是进行搜索窗口更新的函数.par-do指的是并行地执行for循环中的代码.j.Treesplit()指的是在传递参数给空闲处理器j,在j中执行Treesplit()函数,并将处理结果返回到当前处理器.
主处理器在等待其子处理器搜索完成时处于空闲状态,为了利用这段空闲时间,Tree Splitting 算法可以引入如下的改进:
1.在空闲时,主处理器执行从算法,搜索那些由于找不到空闲子处理器而没有被搜索的子树.
2.主处理负责博弈树中的多层结点,以减少空闲时间.
3.主处理可以将一个子结点的所有子结点都分配给一个子处理器,这不仅减少了子处理器的空闲时间,而且能使得alpha值和beta值的共享更方便.
在Tree Splitting算法中,只有离根结点的深度少于k的结点才能并行搜素.其中k为处理器树的深度.由于在一个最好或者较好的移动找到前,可能大量无用的搜索已经并行完成了,所以算法的搜索开销比较大.
6.4 PVSplit算法
主变量分裂(Principal Variation Splitting,简称PVSplit)算法是对Tree Splitting算法的一种改进.它的基本思想是,直到最左路径搜索完成之后,才进行博弈树树的分裂(tree decomposition),开始并行搜索.与TreeSplitting算法一样,PVSplit算法也使用处理器树的方式来管理处理器,分配对博弈树的搜索.
一个PVSplit算法的伪代码如下所示[6]:
PVSplit(node, alpha, beta)
1: if(node.depth = 0) then
2: return(TreeSplit(p,alpha, beta))
3: new_node ← Traverse(node,node.branch[1])
4: alpha ← -PVSplit(new_node,-beta, -alpha)
5: ifalpha ≥ betathen
6: returnalpha
7: fori ← 2 to node.branch.length par-do
8: while(a slave j is idle) do
9: new_node ← Traverse(node,node.branch[i])
10: value[i] ← -j.TreeSplit(new_node,-beta, -alpha)
11: critical begin
12: if (value[i] > alpha) then
13: alpha ← value[i]
14: critical end
15: if (alpha ≥ beta) then
16: Terminate()
17: return alpha
18: returnalpha
PVSplit算法对强有序的博弈树的搜索效率较高.试验表明,在搜索强有序博弈树时,PVSplit算法比Tree Splitting算法的性能优越,尤其是在处理器树较宽的时候.衡量处理器树宽度的指标是处理器树的扇出(fan-out),即每个处理器所拥