The best ways to speed up your code is to listen to hgm or Gerd Isenberg.pedrojdm2021 wrote: ↑Sat Jul 10, 2021 3:12 amHey! thank you for the very useful info! i am going to try that, in C# when you have arr[X][Y] is usually an "Array inside array" aka "Jagged arrays" multi-dimensional arrays in C# are: arr[X,Y], but i do agree that a single-dimensional array will be better.Pio wrote: ↑Sat Jul 10, 2021 1:35 amI think you can speed up the evaluation with some small tricks:pedrojdm2021 wrote: ↑Fri Jul 09, 2021 10:51 pmsure i did profiling, with the following results:Ras wrote: ↑Fri Jul 09, 2021 9:26 pmOn my hardware, my engine has roughly 20M NPS for perft in the initial position, and 1.8M NPS for regular search. The move generator logic is the same as yours, with make/unmake, and the "in check" detection is without bitboards. Instead, with dedicated offset checks or even loops for sliders.
If you get from 8M in perft down to 300k in search, I don't think it's the move generation. Did you profile your code to see where the time really gets spent? With a similar perft-to-search ratio as I have, your search should be around 700k NPS, not 300k.
My evaluation implementation is this:
but in visual studio it does not point to any relevant "hot point". In visual studio even doing an bitwise and operation (&) and performing a count bits lookup it represents a 5% in computation time? it was not supposed to be a really fast operation?Code: Select all
[MethodImpl(MethodImplOptions.AggressiveInlining)] public int Evaluate() { mg_eval[ChessPiece.White] = 0; mg_eval[ChessPiece.Black] = 0; eg_eval[ChessPiece.White] = 0; eg_eval[ChessPiece.Black] = 0; int gamePhase = 0; ulong piece_bitboard = 0ul; int bitCount_dummy = 0; int extra_score = 0; //byte square = 0; byte square_side = 0,player = 0, piece = 0, square = 0; for(player = 0; player <= 1; ++player) { for (piece = 1; piece <= 6; ++piece) { piece_bitboard = board.bitboards[player][piece]; while(piece_bitboard > 0) { // update game-phase state gamePhase += GAMEPHASE_WEIGHTS[piece]; square = BitUtility.GetLS1BIndex(piece_bitboard); BitUtility.RemoveLS1B(ref piece_bitboard); square_side = AISettings.square_table[player][square]; // Evaluate material mg_eval[player] += AISettings.mg_material_score[piece]; eg_eval[player] += AISettings.eg_material_score[piece]; // Evaluate piece positional scores switch(piece) { case ChessPiece.Pawn: extra_score = 0; // ---------- [Pawn structure] ------------- // Double pawns penalty // Get the double pawns bit count bitCount_dummy = BitUtility.CountBits(board.bitboards[player][piece] & ChessData.File_masks[square]); // Verify existence of double pawns and apply penalty if (bitCount_dummy > 0) { mg_eval[player] += bitCount_dummy * AIData.Double_pawn_penalty_opening; eg_eval[player] += bitCount_dummy * AIData.Double_pawn_penalty_ending; } // Isolated pawns penalty // verify existence of isolated pawns and apply penalty if ((board.bitboards[player][piece] & ChessData.Isolated_masks[square]) == 0) { mg_eval[player] += AIData.Isolated_pawn_penalty_opening; eg_eval[player] += AIData.Isolated_pawn_penalty_ending; } // Passed pawns bonus // Verify existence of passed pawns and apply bonus if ((board.bitboards[player ^ 1][piece] & ChessData.Passed_masks[player][square]) == 0) { extra_score += AIData.PassedPawnBonus[ChessData.RanksIndices[square_side]]; } // --------------------------------------- // Pawn square tables mg_eval[player] += AISettings.mg_pawn_table[square_side] + extra_score; eg_eval[player] += AISettings.eg_pawn_table[square_side] + extra_score; break; case ChessPiece.Bishop: // ---------- [Bishop Mobility] ------------ // Get mobility count bitCount_dummy = BitUtility.CountBits(ChessData.GetBishopMoves(square , board.totalOccupancy)); // apply mobility bonus mg_eval[player] += (bitCount_dummy - 4) * 5; eg_eval[player] += (bitCount_dummy - 4) * 5; // ------------------------------------------ // Bishop square tables mg_eval[player] += AISettings.mg_bishop_table[square_side]; eg_eval[player] += AISettings.eg_bishop_table[square_side]; break; case ChessPiece.Rook: // ---------- [Rook Structure] ------------------ extra_score = 0; // Semi open file scoring if ((board.bitboards[player][ChessPiece.Pawn] & ChessData.File_masks[square]) == 0) extra_score += AIData.Semi_open_file_score; // Open file scoreing if (((board.bitboards[player][ChessPiece.Pawn] | board.bitboards[player ^ 1][ChessPiece.Pawn]) & ChessData.File_masks[square]) == 0) extra_score += AIData.Open_file_score; // ---------------------------------------------- // Rook square tables mg_eval[player] += AISettings.mg_rook_table[square_side] + extra_score; eg_eval[player] += AISettings.eg_rook_table[square_side] + extra_score; break; case ChessPiece.Knight: // Knight square tables mg_eval[player] += AISettings.mg_knight_table[square_side]; eg_eval[player] += AISettings.eg_knight_table[square_side]; break; case ChessPiece.Queen: // ------------ [ Queen mobility ] ------------ // Get mobility count bitCount_dummy = BitUtility.CountBits(ChessData.GetQueenMoves(square , board.totalOccupancy)); // Apply mobility bonus mg_eval[player] += (bitCount_dummy - 9); eg_eval[player] += (bitCount_dummy - 9) * 2; // ---------------------------------------------- // Queen square tables mg_eval[player] += AISettings.mg_queen_table[square_side]; eg_eval[player] += AISettings.eg_queen_table[square_side]; break; case ChessPiece.King: extra_score = 0; // ------------ [ King Structure ] ------------ // Semi open file scoring (penalty) if ((board.bitboards[player][ChessPiece.Pawn] & ChessData.File_masks[square]) == 0) extra_score -= AIData.Semi_open_file_score; // Open file scoreing (penalty) if (((board.bitboards[player][ChessPiece.Pawn] | board.bitboards[player ^ 1][ChessPiece.Pawn]) & ChessData.File_masks[square]) == 0) extra_score -= AIData.Open_file_score; // ------------ [ King Safety ] ---------------- extra_score += BitUtility.CountBits(ChessData.GetKingMoves(square) & board.occupancies[player]) * AIData.King_shield_bonus; // --------------------------------------------- // King square tables mg_eval[player] += AISettings.mg_king_table[square_side] + extra_score; eg_eval[player] += AISettings.eg_king_table[square_side] + extra_score; break; } } } } if (gamePhase > 24) gamePhase = 24; int mg_score = (mg_eval[ChessPiece.White] - mg_eval[ChessPiece.Black]) * GetPlayerSide(board.sideToMove); int eg_score = (eg_eval[ChessPiece.White] - eg_eval[ChessPiece.Black]) * GetPlayerSide(board.sideToMove); int eg_phase = 24 - gamePhase; return (mg_score * gamePhase + eg_score * eg_phase) / 24; }
1) You should be able to speed up the two dimensional arrays to one dimensional ones. I guess that is why the profiler said that your bitcount function was slow, not because it was so slow but because you did the two dimensional access within the function call.
2) You can reduce a couple of branches such as
if (bitCount_dummy > 0)
{
mg_eval[player] += bitCount_dummy * AIData.Double_pawn_penalty_opening;
eg_eval[player] += bitCount_dummy * AIData.Double_pawn_penalty_ending;
}
Just remove the if case since multiplying with 0 is zero. Test to see which is faster.
3) Remove the array access for example mg_eval[player] and replace it with mg_eval_white and mg_eval_black. Then you can remove unnecessary array accesses. If you want the code tidier I would probably just put a bool in the Evaluate function and written Evaluate(isWhite)-Evaluate(!isWhite)
about the branches, yes, i am going to review it.
another question, do you know an efficient way to detect if the king is in check?
i use this technique:
https://www.chessprogramming.org/Square ... ck_by_Side
It works great, it is bug free
but i feel that is very slow
i did a benchmark in kiwipete position for that function call ( IsSquareAttacked(sq,player) ) and it reports nearly 40 cpu ticks for a single call
You can speed up IsSquaredAttack by something like this
Code: Select all
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsSquareAttacked(byte _square, byte _currentPlayer)
{
// Attacked by bishops or queens
if((ChessData.GetBishopMoves(_square, totalOccupancy) & (bitboards[_currentPlayer ^ 1][ChessPiece.Bishop] | bitboards[_currentPlayer ^ 1][ChessPiece.Queen]))> 0 )
return true;
// Attacked by rooks or queens
if((ChessData.GetRookMoves(_square, totalOccupancy) & (bitboards[_currentPlayer ^ 1][ChessPiece.Rook] | bitboards[_currentPlayer ^ 1][ChessPiece.Queen])) > 0)
return true;
// Attacked by knights
if((ChessData.GetKnightMoves(_square) & bitboards[_currentPlayer ^ 1][ChessPiece.Knight]) > 0 )
return true;
// Attacked by kings
if((ChessData.GetKingMoves(_square) & bitboards[_currentPlayer ^ 1][ChessPiece.King]) > 0)
return true;
// Attacked by pawns
if ((ChessData.GetPawnAttacks(_square, _currentPlayer) & bitboards[_currentPlayer ^ 1][ChessPiece.Pawn]) > 0)
return true;
return false;
}
Good luck!
/Pio