需要优化 - 算法实现占用大量内存

时间:2013-01-22 04:15:35

标签: c++ memory memory-management recursion destructor

好的,伙计们:内存优化肯定不是我的事情,因为我目前正在开发一个大型,CPU和内存密集型项目,我想我需要你的帮助。

该项目是一个国际象棋引擎,实际问题在于(我猜)以下两种方法之一(上面的代码不是100%准确,但它或多或少):


树搜索(MiniMax - 实际代码是具有不同添加内容的Alpha-Beta,但这个非常基本的示例更具说明性:

int Board::miniMax_(int ply)
{
    if (ply == this->searchDepth) return this->eval();

    int best = MINUS_INF;

    vector<Move*> moves = this->possibleMoves();

    FOREACH(Move*, move, moves)
    {
        HASHMAKE((*move),this);
        int val = -this->miniMax_(ply+1);
        UNHASHMAKE((*move),this);

        if (val>best) { 
            if (ply==0) this->bestMove = (*move);
            best = val; 
        }

    }
    return best;
}

移动世代 (如果你还没有玩过国际象棋编程和位板,下面的内容可能看起来几乎没有感觉;但你仍然可以得到这个想法记忆处理 - 好吧,希望......)

vector<Move*> Board::possibleMoves()
{
    U64 ownPieces = this->piecesForColor(this->playing);
    U64 occupied = ~(this->pieces[empty]);

    vector<Move*> moves;

    //-----------------------------------------
    // "Normal" Piece Moves
    //-----------------------------------------

    const int from = (1+(this->playing))*3;
    const int to = (1+(this->playing))*3+6;

    for (int pieceType=from; pieceType<to; pieceType++)
    {
        U64 piece = this->pieces[pieceType];

        for (; piece != 0; piece &= (piece - 1))
        {
            UINT pos = log2(piece & ~(piece-1));

            U64 move;
            switch (pieceType)
            {
                case bPawns:    move = BPAWN_(pos,ownPieces,occupied); break;
                case wPawns:    move = WPAWN_(pos,ownPieces,occupied); break;
                case bRooks:
                case wRooks:    move = ROOK_(pos,ownPieces,occupied); break;
                case bKnights:
                case wKnights:  move = KNIGHT_(pos,ownPieces,occupied); break;
                case bBishops:
                case wBishops:  move = BISHOP_(pos,ownPieces,occupied); break;
                case bQueen:
                case wQueen:    move = QUEEN_(pos,ownPieces,occupied); break;
                case bKing:
                case wKing:     move = KING_(pos,ownPieces,occupied); break;
                default:break;
            }

            for (; move !=0; move &= (move-1))
            {
                moves += new Move(pos, log2(move&~(move-1)),this);
            }
        }
    }

    return moves;
}

Move类

//=======================================================
// Prototype
//=======================================================

class Move
{
    public:
        Move (string m, Board* b) : from(ALG2FROM(m)), to(ALG2TO(m)), pieceFrom(b->atPosition[from]), pieceTo(b->atPosition[to]) {}
        Move (int f, int t, Board* b) : from(f), to(t), pieceFrom(b->atPosition[from]), pieceTo(b->atPosition[to]) {}
        Move (int val) : value(val) {}

        inline string notation();
        inline string out();
        //----------------------
        int from, to;
        int pieceFrom, pieceTo;
        int value;
};

//=======================================================
// Inline Functions
//=======================================================

inline string Move::notation()
{
    return SSTR(SPOS(this->from) << SPOS(this->to));
}

inline string Move::out()
{
    return SSTR(this->notation() << " :: " << this->value);
}

显然,第一个函数是递归的并且被调用了数百万次,有一些预期的负载。事情是,一旦搜索达到第4,第5层或者其他什么,该应用程序已经占用了2GB。事情是,一旦它完成(搜索),内存仍然没有被释放 - 所以我认为这个表明存在问题。

那么,有什么想法吗?


如果您需要了解有关实施的任何其他信息,请告诉我。


提示:

  • FOREACH只是矢量迭代器
  • 的宏
  • 向量追加的+=来自Boost
  • 粗体的所有内容都是一个宏,但就内存开销而言,它们都没有做任何密集的事情(所以我决定省略它们)
  • 没有任何析构函数实现

3 个答案:

答案 0 :(得分:3)

下面:

vector<Move*> moves = this->possibleMoves();

指针向量不会释放指针。您可以尝试使用std::unique_ptr<Move>的矢量。

如果您不单独分配动作,您将获得更好的表现。使用内存池。在这方面,你可能根本不需要矢量。

除非Move是多态类,否则我建议你根本不分配它。相反,在Set上制作一堆Move函数并声明您的possibleMoves函数:

void Board::possibleMoves( vector<Move> & moves )

显然可以这样称呼:

vector<Move> moves;
possibleMoves( moves );

因此,这意味着当您添加移动而不是执行new时,您可以执行以下操作:

    moves.push_back( Move(pos, log2(move&~(move-1)),this) );

调用复制构造函数。如果你想避免额外的副本,那么为Move创建一个空构造函数并制作上述'setter'函数:

    moves.resize( moves.size()+1 );
    Move & m = moves.back();
    m.Set( pos, log2(move&~(move-1)),this );

我不能100%确定这是否会更快。无论如何,另一件事......如果您希望典型的电路板几乎总是少于50个可能的移动,那么您可以在possibleMoves的开头执行此操作来提高性能:

moves.reserve(50)

这意味着矢量几乎不需要调整大小,从而使push_back操作更快。

答案 1 :(得分:2)

我不确定在这种情况下std :: vector是最好的容器。

可能的移动次数是有限的,应该可以计算它。我不打算猜测你是怎么做的,但假设你可以做到(或者只是使用一个const大数字)......那么可能值得使用数组:

Move *moves = new Move[maxMoves];
FillPossibleMoves(moves, maxMoves)
// do stuff
delete [] moves;

然后您可以将可能的移动功能更新为:

int Board::FillPossibleMoves(Move *moves, int maxMoves)
{
    int i = 0;
    ...
    moves[i].Something = Whatever;
    i++;
    ...
    return i;
}

这样你只需要分配一次内存,并在完成后清理它。


如果您同意:Using arrays or std::vectors in C++, what's the performance gap?表示要避免使用动态数组,那么:

std::vector<Move> moves(maxMoves);
// if you use a const, then you can keep declaration above as a member
// and call moves.clear() here instead
int movesAdded = board->FillPossibleMoves(moves);
// do stuff


int Board::FillPossibleMoves(std::vector<Move> &moves)
{
    int i = 0;
    ...
    moves[i].Something = Whatever;
    i++;
    ...
    return i;
}

答案 2 :(得分:0)

  

没有任何析构函数实现

为什么你认为应该释放内存。你整整都在调用新的东西,但你永远不会删除你创建的Move

另外,向我们展示Move

的定义