由于创建新对象而导致 C++ 内存不足
Posted
技术标签:
【中文标题】由于创建新对象而导致 C++ 内存不足【英文标题】:Running out of memory in C++ due to making new objects 【发布时间】:2017-03-15 22:05:00 【问题描述】:所以我正在尝试为奥赛罗游戏实现蒙特卡洛搜索树。我有一个根节点和子节点,如果您可以在一次合法移动中从“y”移动到“x”,则“x”是“y”的子节点。
在每个节点上,我存储一个“Board”对象,该对象包含所有棋盘信息,例如每个图块的值。我遇到的第一个问题是,如果我更改了子节点的板对象,它也会更改父节点的值。我通过为每个子节点创建一个“新”板对象来解决此问题,但这会导致在我运行模拟数千次时使用过多的内存,以至于内存不足。
如果有一种方法可以更改子节点中的棋盘信息而不更改父节点的棋盘信息,或者是否有更好的方法将棋盘信息存储在每个节点而不是创建每个节点都有一个新的 Board 对象。
如果有什么需要澄清的,请在下方评论,感谢阅读!
编辑:
for (int x = 0; x < numberOfChildren; x += 1)
// Resets *currentBoard to the same state as the node being expanded
Board *currentBoard = nodeToExpand->getCurrentBoard();
// Retrives the board value information
int** temporaryBoardValues = currentBoard->getBoardValues();
// Makes a new board object with the previous parameters
Board *temporaryBoard = new Board(blockSize, boardSize, offset);
// Sets the new board values to the same as the old ones
temporaryBoard->setBoardValues(temporaryBoardValues);
// Creates a clone of that board state
// Board *temporaryBoard = cloneBoard(*currentBoard);
// Creates a node with the cloned board state, setting the parent to be the node being expanded.
// Assigns it one of the available moves
// Produces the array of child nodes
myChildren[x] = new Node(nodeToExpand, temporaryBoard, availableMoves[x], currentPlayer);
//delete temporaryBoard;
小代码sn-p。它是我创建一个用完所有内存的新 Board 对象的部分。
【问题讨论】:
你能发布一个小代码示例吗? 对于深度为n
的分支树搜索:您只需要在内存中保留 (1) 当前最佳移动和n
节点。您不需要只保留整个树,只保留沿着 1 个分支到深度 n
的当前搜索/评估,这可以很容易地保存在堆栈中 - 如果您在堆上保存了许多板,那么您可能做错了。 (另见 alpha-beta 算法)。
那么为什么不将 moves 存储在节点中,并简单地在节点 n 处从所需的序列节点中的移动序列构建棋盘从根到节点n?
@MatthewFennell 建议——在您编写一行代码之前,您的设计中应该考虑到内存要求。最终可能发生的事情是不得不废弃很多,如果不是所有当前代码的话。
从对蒙特卡洛搜索树的***文章的非常肤浅的阅读来看,在我看来,这是一种很好的理论方法,但却是一种糟糕的实践方法。 YMMV。
【参考方案1】:
蒙特卡洛树搜索 (MCTS) 的典型实现不使用任何技巧来明确避免内存不足。从理论上讲,如果你继续模拟,你确实会耗尽内存,但这通常需要的不仅仅是你在 OP 中提到的几千次模拟。
现在,MCTS 的大多数实现仅在每次模拟时将树扩展一个节点。您发布的代码看起来像是在每次模拟中将b
节点添加到树中,其中b
是分支因子(子节点数)。所以这是你可以考虑改变的事情。
此外,您可以查看您在 Board 类中存储的内容,并确保您确实只有必要的内容,仅此而已。只是游戏状态的数据,仅此而已。例如,确保其中没有任何仅为 GUI 所必需的数据(这是我多年前犯的一个错误)。
如果您在查看这两点后仍然存在内存问题,您可以考虑 jaggedSpire 评论的建议。您可以存储移动而不是棋盘状态,并在模拟中通过节点时重新构建棋盘状态。这将显着减少内存消耗,但也会增加每次模拟的处理时间。如果您每回合的思考时间有限,这可能会导致玩家较弱。
最后,考虑到您正在使用我在您发布的代码中看到的new
操作进行手动内存管理,您总是有可能在某处忘记了匹配的delete
并发生内存泄漏。如果您在查看上述几点后仍然存在仅几千次模拟的内存问题,这很可能是原因,因为 MCTS 真的不应该遇到内存问题,直到您达到比几千更高的模拟计数。
【讨论】:
我已经设法解决了创建多个板对象的内存泄漏问题,正如您现在提到的,执行 jaggedSpire 提到的关于仅存储动作的操作。我对 MCTS 的一般方法是从根开始,如果它已扩展,则根据它的上置信界限选择正确的子。如果还没有展开,则展开它并进行随机模拟,直到游戏结束。 但我确实有一个问题,即在计算机的每次移动中,它都会生成大量 NEW 节点,但这些节点在每次移动后都保存在内存中,因此游戏进程内存被旧节点信息堵塞,只是我不确定如何释放该内存 @MatthewFennell 你的意思是说内存使用量增加是因为在“真正的”游戏中进行实际移动后没有清理搜索树吗?如果是这样,是的,您需要确保在实际游戏中进行实际移动后删除整个搜索树。或者,您可以将深度 2 处的节点设置为新的根节点,该节点对应于您的一招,加上对手的一招。然后,您仍然需要确保删除该新根之上的节点,以及这些节点之下无法访问的后代 关于最后一点,关于重用搜索树,请参阅 ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=6731713 和 dke.maastrichtuniversity.nl/m.winands/documents/… 。不过,它在两人游戏中的用处将不如在单人游戏中那么有用 ~Dennis Soemers - 是的,我的意思是在“真实”游戏中采取行动之后。问题是所有这些旧节点都是通过在递归函数中调用 NEW Node(...) 创建的,是否仍然可以释放这些节点正在使用的内存?以上是关于由于创建新对象而导致 C++ 内存不足的主要内容,如果未能解决你的问题,请参考以下文章