Python 3.x 在cython类上使用手动deepcopy会导致内存溢出。为什么?

Python 3.x 在cython类上使用手动deepcopy会导致内存溢出。为什么?,python-3.x,cython,pickle,deep-copy,Python 3.x,Cython,Pickle,Deep Copy,我正在使用MCTS算法为棋盘游戏开发一个智能代理。 蒙特卡罗树搜索MCTS是人工智能中一种流行的方法,主要用于围棋、国际象棋等游戏。。。。在该方法中,代理基于状态构建一棵树,该状态是选择当前状态下允许的移动的结果。允许代理在树中搜索有限的时间。在此期间,代理将树扩展到最有希望赢得游戏的节点。 下图显示了该过程: 有关更多信息,请查看此链接: 1- 在树的根节点中,会有一个变量rootstate,它显示游戏的当前状态。根状态的deepcopy用于在深入树时模拟树状态和未来状态 我将此代码用于gam

我正在使用MCTS算法为棋盘游戏开发一个智能代理。 蒙特卡罗树搜索MCTS是人工智能中一种流行的方法,主要用于围棋、国际象棋等游戏。。。。在该方法中,代理基于状态构建一棵树,该状态是选择当前状态下允许的移动的结果。允许代理在树中搜索有限的时间。在此期间,代理将树扩展到最有希望赢得游戏的节点。 下图显示了该过程:

有关更多信息,请查看此链接:

1-

在树的根节点中,会有一个变量rootstate,它显示游戏的当前状态。根状态的deepcopy用于在深入树时模拟树状态和未来状态

我将此代码用于gamestate类的deepcopy,因为由于cython对象与pickle协议的问题,deepcopy不能很好地与cython对象一起工作:

cdef类游戏状态:

# ... other functions

def __deepcopy__(self,memo_dictionary):
    res = gamestate(self.size)
    res.PLAYERS = self.PLAYERS
    res.size = int(self.size)
    res.board = np.array(self.board, dtype=np.int32)
    res.white_groups = deepcopy(self.white_groups) # a module which checks if white player has won the game
    res.black_groups = deepcopy(self.black_groups) # a module which checks if black player has won the game
    # the black_groups and white_groups are also cython objects which the same deepcopy function is implemented for them
    # .... etc
    return res
每当MCTS迭代开始时,状态的deepcopy就会存储在内存中。 出现的问题是,在游戏开始时, 每1秒的迭代次数在2000到3000之间,这是预期的,但是随着游戏树的扩展,每1秒的迭代次数减少到1。当每次迭代需要更多的时间来完成时,情况会变得更糟 完成。当我检查内存使用情况时,我注意到每次调用代理进行搜索时,内存使用率从0.6%增加到90%。我在纯python中实现了相同的算法,它没有这种类型的问题。所以我想是uu deepcopy uu函数导致了这个问题。曾经有人建议我为中的cython对象创建自己的pickle协议,但我对pickle模块不是很熟悉。 有人能给我推荐一些协议来让我的cython对象摆脱这个障碍吗

编辑2:

我添加了代码的一些部分,这可能会更有用。 下面的代码属于unionfind类的deepcopy,用于游戏状态下的白色组和黑色组:

# ... other functions

def __deepcopy__(self,memo_dictionary):
    res = gamestate(self.size)
    res.PLAYERS = self.PLAYERS
    res.size = int(self.size)
    res.board = np.array(self.board, dtype=np.int32)
    res.white_groups = deepcopy(self.white_groups) # a module which checks if white player has won the game
    res.black_groups = deepcopy(self.black_groups) # a module which checks if black player has won the game
    # the black_groups and white_groups are also cython objects which the same deepcopy function is implemented for them
    # .... etc
    return res
这是在允许的时间内运行的搜索功能:

cdef class mctsagent:
    def search(time_budget):
        cdef int num_rollouts = 0
        while (num_rollouts < time_budget):
          state_copy = deepcopy(self.rootstate)
          node, state = self.select_node(state_copy) # expansion runs inside the select_node function
          turn = state.turn()
          outcome = self.roll_out(state)
          self.backup(node, turn, outcome)
          num_rollouts += 1

这个问题很可能就是台词

res.white_groups = deepcopy(self.white_groups) # a module which checks if white player has won the game
res.black_groups = deepcopy(self.black_groups) # a module which checks if black player has won the game
您应该使用第二个参数memo\u dictionary调用deepcopy。这是DeepCopy的记录,记录是否已复制对象。如果没有它,deepcopy会多次复制同一对象,因此会占用大量内存

res.white_groups = deepcopy(self.white_groups, memo_dictionary) # a module which checks if white player has won the game
res.black_groups = deepcopy(self.black_groups, memo_dictionary) # a module which checks if black player has won the game
编辑:刚刚看到@Blckknght已经在评论中指出了这一点


edit2:unionfind看起来主要包含Python对象。作为一个cdef类,而不仅仅是一个普通类,它可能没有太大的价值。此外,您当前的\uuuudeepcopy\uuuuuuuuuuuuu实际上并没有复制这些词典-您应该执行res.parent=deepcopyself.parent、memo\u dictionary等操作。。如果您只是将其作为一个普通类,那么它将自动实现

白色组或黑色组是否包含对gamestate对象的引用?看起来您可能有一个循环引用,并且没有将备忘录内容传递到您内部使用的deepcopy调用中。不,它们有自己的变量。它是一个简单的unionfind数据结构,包含三个字典和一个列表。游戏状态移动会传递给白_组和黑_组。返回到游戏状态的唯一内容是布尔值,表示游戏是否由黑白玩家结束。当您尝试深度复制其中一个白组或黑组时,是否会发生内存爆炸?如果不查看_deepcopy__代码的其余部分,则可能无法诊断问题。同一对象是否可以出现在白色组或黑色组中的多个位置?如果是这样,你可能需要在打电话给deepcopy时传递备忘录内容。实际上,无论代码嵌套如何,您都应该这样做,以防您的gamestate对象陷入更大的嵌套结构中。在每次迭代中,都会将rootstate的副本传递给select_节点。然后这些函数继续在副本上工作,直到游戏状态到达终端节点。然后结果传播回根节点。但结果与国家无关。因为在下一次迭代中,将使用另一个副本。您关于为self.parent使用memo\u字典的建议非常有用。现在它工作得很好。再次感谢@DavidWI,我想如果我将unionfind对象设置为普通的python类,那么gamestate就不能继承unionfind,因为它是python类。如果我错了,请纠正我。@MasoudMasoumiMoghadam你是对的-我没有意识到你需要从unionfind继承遗产。有一件事还没有讨论过。pickle或cpickle模块能与代码一起使用吗?如何使用这些模块使当前代码运行得更快?您建议@DavidW做什么?pickle/cpickle解决的问题比deepcopy在不同版本的计算机上可靠地加载/保存更复杂 f Python。因此,我的猜测是,他们可能会稍微慢一点。如果您需要pickle,那么您编写的代码可以免费提供deepcopy,这很好,但我怀疑使用pickle是否有助于提高速度。这只是一个猜测——我还没有测量过。