意外的高内存使用率,使用std :: stack和std :: map

时间:2014-01-27 12:37:21

标签: c++ memory map stack swap

我正在尝试遍历一棵树,以便访问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();
}

3 个答案:

答案 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 longint。最有可能的是,它也有两个指针(将条目放在一起)。

distanceTable.size() * sizeof(char)

应该是这样的:

distanceTable.size() *
    (2 * sizeof (void *) + sizeof (unsigned long long ) + sizeof (int))

答案 2 :(得分:1)

这里的其他答案都很好,所以我会尝试采用不同的方法:减小游戏树的大小。

game trees上的wiki文章展示了使用Tic-Tac-Toe的示例:

Tic-Tac-Toe Game Tree

请注意,由于可以镜像/反射和旋转棋盘,因此实际上只有3个独特的位置可以开始游戏。根据您尝试解决的问题类型,您可以利用类似的可能性。