我正在尝试遍历一棵树,以便访问4x4滑动拼图的所有可能状态。我写的算法最初是递归的,但由于(显然)非常深的树,这被证明是不可能的。它坠毁并报告了一个段错误。然后我决定重写算法以迭代地完成它的工作,从我所看到的,它工作得很好。然而,一段时间后,由于交换,它开始大幅减速。我做了一些计算,但无法弄清楚所有这些内存使用的来源......
代码发布在下方,但以下是重要功能:
std::stack<char, std::vector<char>> stack
std::map<unsigned long long, int> distanceTable
假设stack
的内存占用量与其所持有的元素数量成正比,并假设map
(元素为pair<unsigned long long, int>
)的内存占用量相同,我打印超出预期的内存占用量:
cout << (stack.size() * sizeof(char) +
distanceTable.size() * sizeof(pair<unsigned long long, int>))/(1<<20) << "MB\n";
并将输出与top
的输出进行比较。当我自己的程序报告大约500MB时,top
报告它占用了我一半以上的内存(4GB)。这是因为我的推理无法解释的因素4。我在这里缺少什么?
代码:
#include <iostream>
#include <map>
#include <stack>
#include <vector>
#include <sstream>
#include "slider.h"
using namespace std;
typedef Slider<4> Slider4;
typedef Slider4::Move Move;
typedef map<unsigned long long, int> Map;
typedef stack<char, std::vector<char>> Stack;
Move const moves[] = {Slider4::N, Slider4::S, Slider4::E, Slider4::W};
Move const opposite[] = {Slider4::S, Slider4::N, Slider4::W, Slider4::E};
int const moveIdx[] = {0, 1, 2, 3};
int const oppositeIdx[] = {1, 0, 3, 2};
Map generateDistanceTable()
{
// non-recursive tree-walker to generate the distance-table
Map distanceTable;
Stack stack;
Slider4 slider;
unsigned long long depth = 1;
stack.push(-1);
distanceTable[slider.hash()]= depth;
while (depth != 0)
{
cout << (stack.size() * sizeof(char) +
distanceTable.size() * sizeof(pair<unsigned long long, int>))/(1ULL<<20) << "MB\n";
int currentMove = stack.top() + 1;
// find next move
while (currentMove != 4)
{
// Try the move
if (!slider.move(moves[currentMove]))
{
++currentMove;
continue;
}
// Check the current state of the puzzle
auto &d = distanceTable[slider.hash()];
if (d != 0)
{ // already encountered this state -> move back
int undoMove = oppositeIdx[currentMove];
slider.moveUnsafe(moves[undoMove]);
++currentMove; // try next move
continue;
}
stack.push(currentMove);
d = ++depth;
currentMove = 0;
}
if (currentMove == 4)
{
int undoMove = oppositeIdx[stack.top()];
slider.moveUnsafe(moves[undoMove]);
--depth;
stack.pop();
}
}
}
int main()
{
Map table = generateDistanceTable();
}
答案 0 :(得分:2)
首先,std::map
特别无效
记忆用法。您插入的每个值都将单独放置
节点,除了值之外,通常包含三个
指针和一些其他信息(MS中的2 char
实现)。另外,通常分配每个节点
分开,所以分配器所需的额外开销必须
加上。在32位系统上,总开销将为
至少20个字节;在64位系统上,40。
对于std::vector
(它是std::stack
的基础),它是
更好,但如果你不使用reserve
预分配,通常会不时重新分配
将容量乘以1.5或2.这意味着它可以
最终占用的内存比必要的多得多。 (也,
根据分配的模式,系统可能不是
能够有效地重用在释放期间释放的内存
重新分配。)
尽管如此,使用std :: vector仍然是首选
使用std::lower_bound
订购,而不是std::map
。
最后,如果您事先知道确切的条目数
vector会有,或者可以建立一些合理的上界,
您可以使用reserve
进行预分配。这可以避免任何风险
将规模扩大一倍。
答案 1 :(得分:1)
地图中的每个条目必须至少包含unsigned long long
和int
。最有可能的是,它也有两个指针(将条目放在一起)。
distanceTable.size() * sizeof(char)
应该是这样的:
distanceTable.size() *
(2 * sizeof (void *) + sizeof (unsigned long long ) + sizeof (int))
答案 2 :(得分:1)
这里的其他答案都很好,所以我会尝试采用不同的方法:减小游戏树的大小。
game trees上的wiki文章展示了使用Tic-Tac-Toe的示例:
请注意,由于可以镜像/反射和旋转棋盘,因此实际上只有3个独特的位置可以开始游戏。根据您尝试解决的问题类型,您可以利用类似的可能性。