/** Copyright (c) Scott Gasch Module Name: testsee.c Abstract: This code is meant to sanity check the SEE routine. It works by using the generator and MakeMove/UnmakeMove to play out a sequence of moves on a single square. It's somewhat useful code but right now I don't really use it because it is too good: it detects things like pieces that are pinned against the king whereas the SEE itself does not. Still, I'd want to run it and verify the discrepencies if I was doing much work in the SEE code. Author: Scott Gasch (scott.gasch@gmail.com) 21 Oct 2004 Revision History: **/ #include "chess.h" // Same reasoning, for IsAttacked/InCheck (board_representation/ // MOVEGEN_MIGRATION.md section 6b's ISATTACKED_BITBOARD toggle): // TestIsAttackedBB below must always be able to call the real mailbox // IsAttacked by name and compare it against IsAttackedBB explicitly, // regardless of which one chess.h's macro currently routes plain // "IsAttacked(...)" calls to elsewhere in the engine. #ifdef IsAttacked #undef IsAttacked #endif #ifdef InCheck #undef InCheck #endif #ifdef TEST_BROKEN ULONG g_uRootOnMove; SCORE DebugSEERecursive(SEARCHER_THREAD_CONTEXT *ctx, MOVE mv) { SCORE i, iMinMax; ULONG x; MOVE mvReply; ULONG uToMove = ctx->sPosition.uToMove; // // Make the move, this is not optional // ASSERT(GET_COLOR(mv.pMoved) == uToMove); if (FALSE == MakeMove(ctx, mv)) { return(INVALID_SCORE); } // // Initialize MinMax to the stand pat score here so that the reply // can be "something else". // iMinMax = ((PawnMaterial(&ctx->sPosition, g_uRootOnMove) + ctx->sPosition.uNonPawnMaterial[g_uRootOnMove]) - (PawnMaterial(&ctx->sPosition, FLIP(g_uRootOnMove)) + ctx->sPosition.uNonPawnMaterial[FLIP(g_uRootOnMove)])); // // Generate the reply moves. // GenerateMoves(ctx, (MOVE){0}, GENERATE_DONT_SCORE); for (x = ctx->sMoveStack.uBegin[ctx->uPly]; x < ctx->sMoveStack.uEnd[ctx->uPly]; x++) { // // Only consider replies that end up on the same sq as the // move. // mvReply = ctx->sMoveStack.mvf[x].mv; ASSERT(SanityCheckMove(&ctx->sPosition, mvReply)); if (mvReply.cTo != mv.cTo) { continue; } i = DebugSEERecursive(ctx, mvReply); if (INVALID_SCORE != i) { if ((ctx->uPly % 2) == 0) { if (i > iMinMax) iMinMax = i; } else { if (i < iMinMax) iMinMax = i; } } } // // Unmake the original move // UnmakeMove(ctx, mv); return(iMinMax); } SCORE DebugSEE(POSITION *pos, MOVE mv) { SCORE iRootBalance = ((PawnMaterial(pos, pos->uToMove) + pos->uNonPawnMaterial[pos->uToMove]) - (PawnMaterial(pos, FLIP(pos->uToMove)) + pos->uNonPawnMaterial[FLIP(pos->uToMove)])); SCORE iAfterExchange = iRootBalance; SEARCHER_THREAD_CONTEXT *ctx = malloc(sizeof(SEARCHER_THREAD_CONTEXT)); g_uRootOnMove = pos->uToMove; if (NULL != ctx) { pos->uDangerCount[BLACK] = pos->uDangerCount[WHITE] = 0; InitializeSearcherContext(pos, ctx); GenerateMoves(ctx, (MOVE){0}, GENERATE_DONT_SCORE); mv.bvFlags |= WouldGiveCheck(ctx, mv); if (InCheck(pos, pos->uToMove)) { mv.bvFlags |= MOVE_FLAG_ESCAPING_CHECK; } iAfterExchange = DebugSEERecursive(ctx, mv); if (iAfterExchange != INVALID_SCORE) { iAfterExchange -= iRootBalance; } free(ctx); } return(iAfterExchange); } #endif #ifdef TEST static int _SeeListEntryCompare(const void *pA, const void *pB) { const SEE_THREESOME *a = (const SEE_THREESOME *)pA; const SEE_THREESOME *b = (const SEE_THREESOME *)pB; if (a->cLoc != b->cLoc) return ((int)a->cLoc - (int)b->cLoc); return ((int)a->pPiece - (int)b->pPiece); } FLAG SeeListsAreEqual(SEE_LIST *pA, SEE_LIST *pB) { // Order-independent: GetAttacks's caller (SEE()) sorts/heaps the // list immediately after it's populated, so a bitboard-based // GetAttacks returning the same *set* of attackers in a different // order is a correct match, not a bug (board_representation/ // MIGRATION.md section 4). Sort a scratch copy of each by // (cLoc, pPiece) before comparing field-by-field. SEE_LIST sA = *pA; SEE_LIST sB = *pB; ULONG u; if (sA.uCount != sB.uCount) return FALSE; qsort(sA.data, sA.uCount, sizeof(sA.data[0]), _SeeListEntryCompare); qsort(sB.data, sB.uCount, sizeof(sB.data[0]), _SeeListEntryCompare); for (u = 0; u < sA.uCount; u++) { if ((sA.data[u].pPiece != sB.data[u].pPiece) || (sA.data[u].cLoc != sB.data[u].cLoc) || (sA.data[u].uVal != sB.data[u].uVal)) { return FALSE; } } return TRUE; } // This used to diff _GetAttacksBB against the mailbox asm GetAttacks and // the C SlowGetAttacks reference implementation; both were retired // 2026-09-06 (board_representation/MIGRATION.md section 7) once that // three-way comparison had run clean for long enough. With no second // implementation left to diff against, this is now just a crash/ // no-degenerate-output smoke test plus a standalone cycles/call number. void TestGetAttacks(void) { POSITION pos; ULONG u; COOR c; SEE_LIST rgBBList; ULONG color; Trace("Testing GetAttacks...\n"); for (u = 0; u < 20000; u++) { GenerateRandomLegalPosition(&pos); FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) continue; for (color = BLACK; color <= WHITE; color++) { _GetAttacksBB(&rgBBList, &pos, c, color); ASSERT(rgBBList.uCount <= ARRAY_LENGTH(rgBBList.data)); } } } // // Speed: standalone cycles/call number, three positions spanning // piece density (opening/middlegame/endgame). No longer a // comparison (nothing left to compare against), just a number to // watch for regressions over time. { static const char *rgszFen[3] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1", "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1", }; static const char *rgszLabel[3] = { "opening ", "middlegame", "endgame ", }; POSITION posBench; SEE_LIST rgList; UINT64 u64BBTotal, u64Start; ULONG uIter; ULONG uSq; COOR cBench; ULONG uSide; const ULONG uCallsPerPosition = 200000; Trace("Benchmarking _GetAttacksBB (%lu calls/position)...\n", uCallsPerPosition); for (u = 0; u < 3; u++) { FenToPosition(&posBench, (char *)rgszFen[u]); u64BBTotal = 0; for (uIter = 0; uIter < uCallsPerPosition; uIter++) { uSq = uIter % 64; cBench = BIT_NUMBER_TO_COOR(uSq); uSide = uIter & 1; if (!IS_ON_BOARD(cBench)) continue; u64Start = SystemReadTimeStampCounter(); _GetAttacksBB(&rgList, &posBench, cBench, uSide); u64BBTotal += (SystemReadTimeStampCounter() - u64Start); } printf(" %s: _GetAttacksBB %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call\n", rgszLabel[u], u64BBTotal / uCallsPerPosition); } } } // board_representation/MOVEGEN_MIGRATION.md section 6b: direct // comparison harness for IsAttacked/IsAttackedBB, same pattern as // TestGetAttacks above -- mailbox vs. bitboard, same inputs, must // match exactly. This is the piece ExposesCheckBB didn't get before // shipping and had to be debugged the hard way instead (two real bugs // found via TestSan/perft/DEBUG-assert failures rather than a direct // comparison); doing it properly here from the start. void TestIsAttackedBB(void) { POSITION pos; ULONG u; COOR c; ULONG uSide; FLAG fMailbox, fBB; Trace("Testing IsAttacked...\n"); for (u = 0; u < 20000; u++) { GenerateRandomLegalPosition(&pos); FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) continue; for (uSide = BLACK; uSide <= WHITE; uSide++) { fMailbox = IsAttacked(c, &pos, uSide); fBB = IsAttackedBB(c, &pos, uSide); if (fMailbox != fBB) { UtilPanic(TESTCASE_FAILURE, &pos, "IsAttacked/IsAttackedBB mismatch", NULL, NULL, __FILE__, __LINE__); } } } for (uSide = BLACK; uSide <= WHITE; uSide++) { fMailbox = InCheck(&pos, uSide); fBB = InCheckBB(&pos, uSide); if (fMailbox != fBB) { UtilPanic(TESTCASE_FAILURE, &pos, "InCheck/InCheckBB mismatch", NULL, NULL, __FILE__, __LINE__); } } } // // Speed: same isolated cycles/call methodology as TestGetAttacks // above, interleaved call-by-call to cancel shared-box noise. // { static const char *rgszFen[3] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1", "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1", }; static const char *rgszLabel[3] = { "opening ", "middlegame", "endgame ", }; POSITION posBench; UINT64 u64MailboxTotal, u64BBTotal, u64Start; ULONG uIter; ULONG uSq; COOR cBench; ULONG uSideBench; const ULONG uCallsPerPosition = 200000; FLAG fSink; Trace("Benchmarking IsAttacked: mailbox vs IsAttackedBB " "(interleaved, %lu calls/position)...\n", uCallsPerPosition); for (u = 0; u < 3; u++) { FenToPosition(&posBench, (char *)rgszFen[u]); u64MailboxTotal = 0; u64BBTotal = 0; for (uIter = 0; uIter < uCallsPerPosition; uIter++) { uSq = uIter % 64; cBench = BIT_NUMBER_TO_COOR(uSq); uSideBench = uIter & 1; if (!IS_ON_BOARD(cBench)) continue; u64Start = SystemReadTimeStampCounter(); fSink = IsAttacked(cBench, &posBench, uSideBench); u64MailboxTotal += (SystemReadTimeStampCounter() - u64Start); u64Start = SystemReadTimeStampCounter(); fSink = IsAttackedBB(cBench, &posBench, uSideBench); u64BBTotal += (SystemReadTimeStampCounter() - u64Start); (void)fSink; } printf(" %s: mailbox %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call, IsAttackedBB %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call " "(BB is %.2fx mailbox)\n", rgszLabel[u], u64MailboxTotal / uCallsPerPosition, u64BBTotal / uCallsPerPosition, (double)u64BBTotal / (double)u64MailboxTotal); } } } // SeeFastSign (see.c) is a sign-only shortcut for SEE(pos, mv) >= 0 -- // GetMoveSee's only consumer (ComputeMoveExtension, searchsup.c) never // reads the magnitude, only the sign, so this skips SEE()'s list- // build/heap/xray work whenever the defending side has zero attackers // on the target square (a case where SEE()'s own exchange loop would // terminate on its first iteration anyway). Correctness requirement, // per Scott: assert the sign always matches what real SEE() returns -- // this isn't meant to be an approximation, so any mismatch is a real // bug in the fast path, not tolerable noise. void TestSeeFastSign(void) { SEARCHER_THREAD_CONTEXT *ctx; POSITION posBench; MOVE rgmv[MAX_MOVE_STACK]; ULONG uMoveCount; ULONG u, x; FLAG fFast, fReal; const ULONG uPositions = 3000; Trace("Testing SeeFastSign (sign vs real SEE)...\n"); ctx = (SEARCHER_THREAD_CONTEXT *)malloc(sizeof(SEARCHER_THREAD_CONTEXT)); if (NULL == ctx) { return; } for (u = 0; u < uPositions; u++) { GenerateRandomLegalPosition(&posBench); InitializeSearcherContext(&posBench, ctx); GenerateMoves(ctx, (MOVE){0}, GENERATE_DONT_SCORE); uMoveCount = 0; for (x = ctx->sMoveStack.uBegin[ctx->uPly]; x < ctx->sMoveStack.uEnd[ctx->uPly]; x++) { rgmv[uMoveCount++] = ctx->sMoveStack.mvf[x].mv; } for (x = 0; x < uMoveCount; x++) { fFast = SeeFastSign(&(ctx->sPosition), rgmv[x]); fReal = (SEE(&(ctx->sPosition), rgmv[x]) >= 0); if (fFast != fReal) { Trace("move=0x%x from=%d to=%d moved=%d captured=%d " "promoted=%d fFast=%d fReal=%d real_see=%d\n", rgmv[x].uMove, rgmv[x].cFrom, rgmv[x].cTo, rgmv[x].pMoved, rgmv[x].pCaptured, rgmv[x].pPromoted, fFast, fReal, SEE(&(ctx->sPosition), rgmv[x])); DumpPosition(&(ctx->sPosition)); UtilPanic(TESTCASE_FAILURE, &(ctx->sPosition), "SeeFastSign/SEE sign mismatch", NULL, NULL, __FILE__, __LINE__); } } } free(ctx); // // Speed: same isolated, interleaved cycles/call methodology as the // other benchmarks above, real generated moves at each benchmark // position (same setup as TestSEESpeed). // { static const char *rgszFen[3] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1", "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1", }; static const char *rgszLabel[3] = { "opening ", "middlegame", "endgame ", }; SEARCHER_THREAD_CONTEXT *ctx2; POSITION posBench2; MOVE rgmv2[MAX_MOVE_STACK]; ULONG uMoveCount2; UINT64 u64RealTotal, u64FastTotal, u64Start; ULONG uIter; FLAG fSink; const ULONG uCallsPerPosition = 200000; Trace("Benchmarking SEE sign: real SEE()>=0 vs SeeFastSign " "(interleaved, %lu calls/position)...\n", uCallsPerPosition); ctx2 = (SEARCHER_THREAD_CONTEXT *)malloc(sizeof(SEARCHER_THREAD_CONTEXT)); if (NULL == ctx2) { return; } for (u = 0; u < 3; u++) { FenToPosition(&posBench2, (char *)rgszFen[u]); InitializeSearcherContext(&posBench2, ctx2); GenerateMoves(ctx2, (MOVE){0}, GENERATE_DONT_SCORE); uMoveCount2 = 0; for (x = ctx2->sMoveStack.uBegin[ctx2->uPly]; x < ctx2->sMoveStack.uEnd[ctx2->uPly]; x++) { rgmv2[uMoveCount2++] = ctx2->sMoveStack.mvf[x].mv; } if (uMoveCount2 == 0) { continue; } u64RealTotal = 0; u64FastTotal = 0; for (uIter = 0; uIter < uCallsPerPosition; uIter++) { ULONG uPair = uIter % uMoveCount2; u64Start = SystemReadTimeStampCounter(); fSink = (SEE(&(ctx2->sPosition), rgmv2[uPair]) >= 0); u64RealTotal += (SystemReadTimeStampCounter() - u64Start); u64Start = SystemReadTimeStampCounter(); fSink = SeeFastSign(&(ctx2->sPosition), rgmv2[uPair]); u64FastTotal += (SystemReadTimeStampCounter() - u64Start); (void)fSink; } printf(" %s: real-SEE %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call, SeeFastSign %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call " "(fast is %.2fx real, %u distinct moves)\n", rgszLabel[u], u64RealTotal / uCallsPerPosition, u64FastTotal / uCallsPerPosition, (double)u64FastTotal / (double)u64RealTotal, uMoveCount2); } free(ctx2); } } // _WhoAttacksSquareMagicBB (see.c) replaces _WhoAttacksSquareBB's // manual per-direction ray-walk with the same O(1) magic-bitboard // lookups (_RookAttacksBB/_BishopAttacksBB, generate.c) already used // everywhere else in this codebase. Same correctness+speed pattern as // the other Test* functions here. void TestWhoAttacksSquareMagic(void) { POSITION pos; ULONG u; COOR c; ULONG uSide; BITBOARD bbRay; BITBOARD bbMagic; const ULONG uPositions = 20000; Trace("Testing _WhoAttacksSquareMagicBB (magic vs ray-walk)...\n"); for (u = 0; u < uPositions; u++) { GenerateRandomLegalPosition(&pos); FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) continue; for (uSide = BLACK; uSide <= WHITE; uSide++) { bbRay = _WhoAttacksSquareRayBB(&pos, c, uSide, pos.bbOccupied); bbMagic = _WhoAttacksSquareBB(&pos, c, uSide, pos.bbOccupied); if (bbRay != bbMagic) { Trace("cSquare=%d uSide=%lu bbRay=0x%llx bbMagic=0x%llx\n", c, uSide, bbRay, bbMagic); UtilPanic(TESTCASE_FAILURE, &pos, "_WhoAttacksSquareBB/ray-walk mismatch", NULL, NULL, __FILE__, __LINE__); } } } } // // Speed: same isolated, interleaved cycles/call methodology as the // other benchmarks above. // { static const char *rgszFen[3] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1", "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1", }; static const char *rgszLabel[3] = { "opening ", "middlegame", "endgame ", }; POSITION posBench; BITBOARD bbSink; UINT64 u64RayTotal, u64MagicTotal, u64Start; ULONG uIter; ULONG uSq; COOR cBench; ULONG uSideBench; const ULONG uCallsPerPosition = 200000; Trace("Benchmarking _WhoAttacksSquareBB: ray-walk vs magic " "(interleaved, %lu calls/position)...\n", uCallsPerPosition); for (u = 0; u < 3; u++) { FenToPosition(&posBench, (char *)rgszFen[u]); u64RayTotal = 0; u64MagicTotal = 0; for (uIter = 0; uIter < uCallsPerPosition; uIter++) { uSq = uIter % 64; cBench = BIT_NUMBER_TO_COOR(uSq); uSideBench = uIter & 1; if (!IS_ON_BOARD(cBench)) continue; u64Start = SystemReadTimeStampCounter(); bbSink = _WhoAttacksSquareRayBB(&posBench, cBench, uSideBench, posBench.bbOccupied); u64RayTotal += (SystemReadTimeStampCounter() - u64Start); u64Start = SystemReadTimeStampCounter(); bbSink = _WhoAttacksSquareBB(&posBench, cBench, uSideBench, posBench.bbOccupied); u64MagicTotal += (SystemReadTimeStampCounter() - u64Start); (void)bbSink; } printf(" %s: ray-walk %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call, magic %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call " "(magic is %.2fx ray-walk)\n", rgszLabel[u], u64RayTotal / uCallsPerPosition, u64MagicTotal / uCallsPerPosition, (double)u64MagicTotal / (double)u64RayTotal); } } } // // Correctness + speed gate for SEE()'s no-defender early exit (see.c): // SEE() must return the EXACT same value as _SEEFull() (not just the // same sign -- TestSeeFastSign above already covers sign-only) on // every move, since real callers (_ScoreAllMoves/_ScoreQSearchMoves*) // trust the magnitude, not just the sign, for a winning capture. // // Speed deliberately uses four benchmark positions spanning the // defended/undefended spectrum, not just a "best case": a dense // tactical middlegame where nearly every capture has a defender (the // new check's overhead pays for nothing -- this is the case to watch // for a net LOSS, since every call still pays for the early check // before falling through to the exact same _SEEFull work SEE() used // to do directly), a bare-kings-plus-one-piece endgame where almost // nothing is defended (the case the early exit is for), plus the // standard opening/middlegame pair for a realistic middle ground. // void TestSEENoDefenderFastPath(void) { SEARCHER_THREAD_CONTEXT *ctx; POSITION posBench; MOVE rgmv[MAX_MOVE_STACK]; ULONG uMoveCount; ULONG u, x; SCORE iFull, iFast; const ULONG uPositions = 3000; Trace("Testing SEE() no-defender fast path against _SEEFull " "(exact value, not just sign)...\n"); ctx = (SEARCHER_THREAD_CONTEXT *)malloc(sizeof(SEARCHER_THREAD_CONTEXT)); if (NULL == ctx) { return; } for (u = 0; u < uPositions; u++) { GenerateRandomLegalPosition(&posBench); InitializeSearcherContext(&posBench, ctx); GenerateMoves(ctx, (MOVE){0}, GENERATE_DONT_SCORE); uMoveCount = 0; for (x = ctx->sMoveStack.uBegin[ctx->uPly]; x < ctx->sMoveStack.uEnd[ctx->uPly]; x++) { rgmv[uMoveCount++] = ctx->sMoveStack.mvf[x].mv; } for (x = 0; x < uMoveCount; x++) { iFull = _SEEFull(&(ctx->sPosition), rgmv[x]); iFast = SEE(&(ctx->sPosition), rgmv[x]); if (iFull != iFast) { Trace("move=0x%x from=%d to=%d moved=%d captured=%d " "promoted=%d _SEEFull=%d SEE=%d\n", rgmv[x].uMove, rgmv[x].cFrom, rgmv[x].cTo, rgmv[x].pMoved, rgmv[x].pCaptured, rgmv[x].pPromoted, iFull, iFast); DumpPosition(&(ctx->sPosition)); UtilPanic(TESTCASE_FAILURE, &(ctx->sPosition), "SEE/_SEEFull exact-value mismatch", NULL, NULL, __FILE__, __LINE__); } } } free(ctx); // // Speed: same isolated, interleaved cycles/call methodology as the // other benchmarks above. // { static const char *rgszFen[4] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1", "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1", }; static const char *rgszLabel[4] = { "opening ", "middlegame ", "tactical/dense ", "sparse endgame ", }; SEARCHER_THREAD_CONTEXT *ctx2; POSITION posBench2; MOVE rgmv2[MAX_MOVE_STACK]; ULONG uMoveCount2; UINT64 u64FullTotal, u64FastTotal, u64Start; ULONG uIter; SCORE iSink; const ULONG uCallsPerPosition = 200000; Trace("Benchmarking SEE(): _SEEFull (no early exit) vs SEE " "(no-defender fast path), interleaved, %lu calls/" "position...\n", uCallsPerPosition); ctx2 = (SEARCHER_THREAD_CONTEXT *)malloc(sizeof(SEARCHER_THREAD_CONTEXT)); if (NULL == ctx2) { return; } for (u = 0; u < 4; u++) { FenToPosition(&posBench2, (char *)rgszFen[u]); InitializeSearcherContext(&posBench2, ctx2); GenerateMoves(ctx2, (MOVE){0}, GENERATE_DONT_SCORE); uMoveCount2 = 0; for (x = ctx2->sMoveStack.uBegin[ctx2->uPly]; x < ctx2->sMoveStack.uEnd[ctx2->uPly]; x++) { rgmv2[uMoveCount2++] = ctx2->sMoveStack.mvf[x].mv; } if (uMoveCount2 == 0) { continue; } u64FullTotal = 0; u64FastTotal = 0; for (uIter = 0; uIter < uCallsPerPosition; uIter++) { ULONG uPair = uIter % uMoveCount2; u64Start = SystemReadTimeStampCounter(); iSink = _SEEFull(&(ctx2->sPosition), rgmv2[uPair]); u64FullTotal += (SystemReadTimeStampCounter() - u64Start); u64Start = SystemReadTimeStampCounter(); iSink = SEE(&(ctx2->sPosition), rgmv2[uPair]); u64FastTotal += (SystemReadTimeStampCounter() - u64Start); (void)iSink; } printf(" %s: _SEEFull %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call, SEE %" COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call " "(SEE is %.2fx _SEEFull, %u distinct moves)\n", rgszLabel[u], u64FullTotal / uCallsPerPosition, u64FastTotal / uCallsPerPosition, (double)u64FastTotal / (double)u64FullTotal, uMoveCount2); } free(ctx2); } } #endif // TEST