for ( all moves) {
clone position to newPosition
score = -alphaBeta( -beta, -alpha, depthleft - 1 );
…
discard newPosition}
instead of
for ( all moves) {
make move;
score = -alphaBeta( -beta, -alpha, depthleft - 1 );
…
undo move}
Last night I added an array of 512 64bit Ints to track the game history (up to 256 full moves) to detect draws per the 3 move rule. Thinking that cloning and discarding millions of Position Objects during search was an increasingly bad idea now that the position Object was over 4k in size I wrote an “undo move” function, which seems to be the norm. To my surprise Running perft 6 moves deep revealed the the cloning method to be marginally quicker.
I am using Java and have no idea how memory is malloced or how the garbage collection works, but simply given that the piece array and position history stack (really the Zorbist key history) were being copied, it seems way off to have this be faster than undo move. Has anyone else tried anything like this? Anyone venture a guess as to what's going on? I'm a hardware guy and have never written code longer than a few hundred lines so I could be way off on this. If so, enlighten me. I have included stats and some code snippets below.
Perft from initial position with undo move function (time in ms):
Search time: 0
Nodes 20, Captues 0, EnPassant 0, Castles 0, Promotions 0, Checks 0, Mates 0
Search time: 14
Nodes 400, Captues 0, EnPassant 0, Castles 0, Promotions 0, Checks 0, Mates 0
Search time: 113
Nodes 8902, Captues 34, EnPassant 0, Castles 0, Promotions 0, Checks 12, Mates 0
Search time: 2099
Nodes 197281, Captues 1576, EnPassant 0, Castles 0, Promotions 0, Checks 469, Mates 0
Search time: 47758
Nodes 4865609, Captues 82719, EnPassant 258, Castles 0, Promotions 0, Checks 27351, Mates 0
Search time: 1193596
Nodes 119060324, Captues 2812008, EnPassant 5248, Castles 0, Promotions 0, Checks 809099, Mates 0
Perft with Cloning function.
Search time: 1
Nodes 20, Captues 0, EnPassant 0, Castles 0, Promotions 0, Checks 0, Mates 0
Search time: 14
Nodes 400, Captues 0, EnPassant 0, Castles 0, Promotions 0, Checks 0, Mates 0
Search time: 108
Nodes 8902, Captues 34, EnPassant 0, Castles 0, Promotions 0, Checks 12, Mates 0
Search time: 1920
Nodes 197281, Captues 1576, EnPassant 0, Castles 0, Promotions 0, Checks 469, Mates 0
Search time: 45269
Nodes 4865609, Captues 82719, EnPassant 258, Castles 0, Promotions 0, Checks 27351, Mates 0
Search time: 1134039
Nodes 119060324, Captues 2812008, EnPassant 5248, Castles 0, Promotions 0, Checks 809099, Mates 0
///Position Object
Code: Select all
class Position implements Cloneable{
int board[]=new int [128]; //0x88 Board representation
long moveHistoryStack[] = new long [512]; //Position Zorbist key History
int moveHistoryPointer;
int whiteKingSq;
int blackKingSq;
int moveNumber;
int hundredHalfMoveCounter; // This is for the 50 move rule
int enPassant; // If en passant capture is legal it's for this column. A column of 8 means no En Passant is possible
long hashKey=0;
int materialSum;
boolean castleKingSideBlack; //castle king side still legal
boolean castleQueenSideBlack; // dito queen side
boolean castleKingSideWhite; //castle king side still legal
boolean castleQueenSideWhite; // dito queen side
boolean whitesMove; //White to move (else black)
boolean whiteCheck; //In check flag
boolean blackCheck; //In check flag
boolean draw; //Draw flag
void undoMove(UnMove unMove){
this.enPassant = unMove.enPassant;
this.materialSum = unMove.materialSum;
this.hundredHalfMoveCounter = unMove.hundredHalfMoveCounter;
this.castleKingSideBlack = unMove.castleKingSideBlack;
this.castleQueenSideBlack = unMove.castleQueenSideBlack;
this.castleKingSideWhite = unMove.castleKingSideWhite;
this.castleQueenSideWhite = unMove.castleQueenSideWhite;
this.blackCheck = unMove.blackCheck;
this.whiteCheck = unMove.whiteCheck;
if (unMove.wasCastles){
switch(unMove.to){
case (2):{ //White castles Q
this.board[0]=W_ROOK;
this.board[4]=W_KING;
this.board[2]=EMPTY;
this.board[3]=EMPTY;
this.whiteKingSq=4;
}
case (6):{ //White castles K
this.board[7]=W_ROOK;
this.board[4]=W_KING;
this.board[5]=EMPTY;
this.board[6]=EMPTY;
this.whiteKingSq=4;
}
case (114):{ //Black castles Q
this.board[112]=B_ROOK;
this.board[116]=B_KING;
this.board[114]=EMPTY;
this.board[115]=EMPTY;
this.blackKingSq=116;
}
case (118):{ //Black castles Q
this.board[119]=B_ROOK;
this.board[116]=B_KING;
this.board[117]=EMPTY;
this.board[118]=EMPTY;
this.blackKingSq=116;
}
}
undoMoveCleanUp();
return;
}
if (unMove.wasEnPassant){
this.board[unMove.to]=EMPTY;
if (this.whitesMove){//side to move has not been undone, so this is blacks move
this.board[unMove.from] = B_PAWN;
this.board[unMove.to+16]=W_PAWN;
}
else{
this.board[unMove.from] = W_PAWN;
this.board[unMove.to-16]=B_PAWN;
}
undoMoveCleanUp();
return;
}
this.board[unMove.from]=this.board[unMove.to];
this.board[unMove.to]=unMove.piece;
if (this.board[unMove.from]==W_KING) this.whiteKingSq=unMove.from;
if (this.board[unMove.from]==B_KING) this.blackKingSq=unMove.from;
if (unMove.wasPromotion) {
if (this.whitesMove)//side to move has not been updated, so this is blacks move
this.board[unMove.from]=B_PAWN;
if (this.whitesMove) this.board[unMove.from]=W_PAWN;
}
undoMoveCleanUp();
return;
}
void undoMoveCleanUp(){
this.whitesMove=!this.whitesMove;
this.moveHistoryPointer--;
this.hashKey=this.moveHistoryStack[moveHistoryPointer];
this.moveNumber--;
}