diff options
| author | Scott Gasch <[email protected]> | 2026-09-05 01:03:28 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-05 01:03:28 -0700 |
| commit | 6e8645015f73a09f57e592fbf911e66db845b327 (patch) | |
| tree | 25206e1a6e442c1e3456051fab57e0c16ca4384b | |
| parent | 57502d6e205bc802fc9b89fd1534add5efc4b9ea (diff) | |
Replace Eval()'s piece-dispatch loop with per-type bitboard walks
The old loop walked cNonPawns[color][1..N] (a flat, arbitrarily-
ordered list mixing knight/bishop/rook/queen), doing a mailbox lookup
plus a p&0x4/IS_KNIGHT branch per piece to decide which _Eval*
function to call, then stashing rooks/queens into cDefer/uDefer
arrays to evaluate in a later pass -- a hard-to-predict branch per
piece on top of a mailbox read the callee already redoes for its own
ASSERT.
pos->bbPieces[color][PIECE_TYPE] already exists (incrementally
maintained by move.c) and was simply unused by Eval() until now.
Replaced the whole dispatch with a direct per-type bitboard walk:
knights, then bishops, for side-to-move, then the same for the other
side, then rooks both colors, then queens both colors -- same phase
order as before (load-bearing for bvAttacks accumulation), just
sourced from a bitboard instead of a mixed list + runtime type
dispatch. Retires the cDefer/uDefer bookkeeping entirely -- with each
type its own direct walk, "rooks after minors" is just "do that walk
after this one," nothing to defer.
Verified genuinely behavior-neutral, not just crash-free: built the
pre-change and post-change binaries side by side and diffed
--batch --command output against tests/ecm_ringers.ep_ at sd10. Node
counts, scores, and PVs are byte-for-byte identical across all 11
positions; only wall-clock time and NPS differ, consistently in the
new version's favor (~3-5% faster on this suite, e.g. 1088239 nps ->
1143141 nps, 1560092 -> 1616844). Also verified via the normal
precommit_check.sh gate.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
| -rwxr-xr-x | src/eval.c | 229 |
1 files changed, 101 insertions, 128 deletions
@@ -4803,14 +4803,11 @@ Return value: **/ { - ULONG uDefer[2][2]; - COOR cDefer[2][2][10]; POSITION *pos = &(ctx->sPosition); SCORE iScoreForSideToMove; SCORE iAlphaMargin, iBetaMargin; PAWN_HASH_ENTRY *pHash; COOR c; - PIECE p; ULONG u; ULONG uColor; BITBOARD bb; @@ -5054,131 +5051,111 @@ Return value: // // Evaluate individual pieces. // + // board_representation/EVAL.md section 0/2: replaces the old + // cNonPawns[color][1..N] walk + per-piece mailbox lookup + p&0x4/ + // IS_KNIGHT branch dispatch (a hard-to-predict branch per piece, + // on top of a redundant mailbox read the callee already redoes + // for its own ASSERT) with a direct per-type location-bitboard + // walk. pos->bbPieces[color][PIECE_TYPE] already exists, + // incrementally maintained by move.c, and was simply unused by + // Eval() until now -- no new infrastructure, purely a consumer + // change. Also retires the cDefer/uDefer "remember the rooks and + // queens for later" bookkeeping entirely: since each type is now + // its own direct bitboard walk, "evaluate rooks after minors" is + // just "do the rook-bitboard walk after the minor-bitboard walks" + // -- nothing to defer. + // + // Phase order preserved exactly as before (side-to-move's minors, + // then the other side's minors, then rooks both colors, then + // queens both colors) -- that ordering is load-bearing for + // bvAttacks accumulation (each piece's mobility/danger depends on + // attack bits already written by earlier-evaluated pieces this + // same Eval() call). Knight-vs-bishop order *within* the same + // color/phase, and encounter order within a single type's own + // bitboard walk, were never meaningful before (cNonPawns' order + // is arbitrary swap-with-last-on-removal, not stable) and stay + // that way -- EVAL_TERM's plain score accumulation doesn't care. + // pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0; - uDefer[BLACK][0] = uDefer[BLACK][1] = - uDefer[WHITE][0] = uDefer[WHITE][1] = 0; uColor = pos->uToMove; - for (u = 1; // skips the king - u < pos->uNonPawnCount[uColor][0]; - u++) + bb = pos->bbPieces[uColor][KNIGHT]; + while (bb) { - c = pos->cNonPawns[uColor][u]; - ASSERT(IS_ON_BOARD(c)); - p = pos->rgSquare[c].pPiece; - ASSERT(IS_VALID_PIECE(p)); - ASSERT(GET_COLOR(p) == uColor); - ASSERT(!IS_PAWN(p) && !IS_KING(p)); - - if (p & 0x4) - { - ASSERT(IS_BISHOP(p) || IS_KNIGHT(p)); - - // - // Note: The side on move's pieces of value X are - // evaluated and have their mobility counted and added to - // the attack table before the other side's pieces. Also, - // we do not care if this piece is in danger b/c we will - // find out later on in EvalLookForDanger anyway. - // - if (IS_KNIGHT(p)) - { - TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight, - _EvalKnight(pos, c, pHash)); - } - else - { - TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop, - _EvalBishop(pos, c, pHash)); - } + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); + TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight, + _EvalKnight(pos, c, pHash)); #ifdef EVAL_DUMP - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); +#endif + } + bb = pos->bbPieces[uColor][BISHOP]; + while (bb) + { + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); + TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop, + _EvalBishop(pos, c, pHash)); +#ifdef EVAL_DUMP + Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif - } - else - { - ASSERT(IS_ROOK(p) || IS_QUEEN(p)); - cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c; - } } uColor = FLIP(uColor); ASSERT(uColor != pos->uToMove); - for (u = 1; - u < pos->uNonPawnCount[uColor][0]; - u++) + bb = pos->bbPieces[uColor][KNIGHT]; + while (bb) { - c = pos->cNonPawns[uColor][u]; - ASSERT(IS_ON_BOARD(c)); - p = pos->rgSquare[c].pPiece; - ASSERT(IS_VALID_PIECE(p)); - ASSERT(GET_COLOR(p) == uColor); - ASSERT(!IS_PAWN(p) && !IS_KING(p)); - if (p & 0x4) - { - // - // This time we are evaluating a piece from the side not - // on move. We will not go back later and look at this - // piece again so if it's in danger then record it. The - // drawback, of course, is that danger is only detected if - // a piece is attacked by an enemy of lesser value! - // - if (IS_KNIGHT(p)) - { - TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight, - _EvalKnight(pos, c, pHash)); - } - else - { - TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop, - _EvalBishop(pos, c, pHash)); - } + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); + TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight, + _EvalKnight(pos, c, pHash)); #ifdef EVAL_DUMP - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); +#endif + } + bb = pos->bbPieces[uColor][BISHOP]; + while (bb) + { + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); + TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop, + _EvalBishop(pos, c, pHash)); +#ifdef EVAL_DUMP + Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif - } - else - { - ASSERT(IS_ROOK(p) || IS_QUEEN(p)); - cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c; - } } // // Evaluate any rook(s) for side on move then for side not on move. // uColor = FLIP(uColor); - for (u = 0; - u < uDefer[uColor][0]; - u++) + bb = pos->bbPieces[uColor][ROOK]; + while (bb) { - c = cDefer[uColor][0][u]; - ASSERT(IS_ON_BOARD(c)); - ASSERT(IS_ROOK(pos->rgSquare[c].pPiece)); - ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash)); #ifdef EVAL_DUMP - p = BLACK_ROOK | uColor; - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } uColor = FLIP(uColor); - for (u = 0; - u < uDefer[uColor][0]; - u++) + bb = pos->bbPieces[uColor][ROOK]; + while (bb) { - c = cDefer[uColor][0][u]; - ASSERT(IS_ON_BOARD(c)); - ASSERT(IS_ROOK(pos->rgSquare[c].pPiece)); - ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash)); #ifdef EVAL_DUMP - p = BLACK_ROOK | uColor; - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } @@ -5186,36 +5163,28 @@ Return value: // Evaluate any queen(s) for side on move then for side not on move. // uColor = FLIP(uColor); - for (u = 0; - u < uDefer[uColor][1]; - u++) + bb = pos->bbPieces[uColor][QUEEN]; + while (bb) { - c = cDefer[uColor][1][u]; - ASSERT(IS_ON_BOARD(c)); - ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece)); - ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash)); #ifdef EVAL_DUMP - p = BLACK_ROOK | uColor; - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } uColor = FLIP(uColor); - for (u = 0; - u < uDefer[uColor][1]; - u++) + bb = pos->bbPieces[uColor][QUEEN]; + while (bb) { - c = cDefer[uColor][1][u]; - ASSERT(IS_ON_BOARD(c)); - ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece)); - ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor); + u = FastFirstBit(bb) - 1; + bb &= (bb - 1); + c = BIT_NUMBER_TO_COOR(u); TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash)); #ifdef EVAL_DUMP - p = BLACK_ROOK | uColor; - Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p), - pos->iScore[WHITE], pos->iScore[BLACK]); + Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]); #endif } @@ -5225,10 +5194,12 @@ Return value: c = pos->cNonPawns[BLACK][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); - p = pos->rgSquare[c].pPiece; - ASSERT(IS_VALID_PIECE(p)); - ASSERT(GET_COLOR(p) == BLACK); - ASSERT(IS_KING(p)); + { + PIECE pDebugKing = pos->rgSquare[c].pPiece; + ASSERT(IS_VALID_PIECE(pDebugKing)); + ASSERT(GET_COLOR(pDebugKing) == BLACK); + ASSERT(IS_KING(pDebugKing)); + } #endif TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash)); ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK] = pos->iTempScore; @@ -5239,10 +5210,12 @@ Return value: c = pos->cNonPawns[WHITE][0]; #ifdef DEBUG ASSERT(IS_ON_BOARD(c)); - p = pos->rgSquare[c].pPiece; - ASSERT(IS_VALID_PIECE(p)); - ASSERT(GET_COLOR(p) == WHITE); - ASSERT(IS_KING(p)); + { + PIECE pDebugKing = pos->rgSquare[c].pPiece; + ASSERT(IS_VALID_PIECE(pDebugKing)); + ASSERT(GET_COLOR(pDebugKing) == WHITE); + ASSERT(IS_KING(pDebugKing)); + } #endif TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash)); ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE] = pos->iTempScore; |
