diff options
Diffstat (limited to 'src/testsee.c')
| -rw-r--r-- | src/testsee.c | 136 |
1 files changed, 125 insertions, 11 deletions
diff --git a/src/testsee.c b/src/testsee.c index da7df59..77fd706 100644 --- a/src/testsee.c +++ b/src/testsee.c @@ -134,16 +134,36 @@ DebugSEE(POSITION *pos, #endif #ifdef TEST -FLAG +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 (pA->uCount != pB->uCount) return FALSE; - for (u = 0; u < pA->uCount; 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 ((pA->data[u].pPiece != pB->data[u].pPiece) || - (pA->data[u].cLoc != pB->data[u].cLoc) || - (pA->data[u].uVal != pB->data[u].uVal)) + 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; } @@ -159,20 +179,21 @@ TestGetAttacks(void) COOR c; SEE_LIST rgSlowList; SEE_LIST rgAsmList; + SEE_LIST rgBBList; ULONG color; - + #if !defined(_X86_) && !defined(_X64_) return; #endif - + Trace("Testing GetAttacks...\n"); for (u = 0; u < 20000; u++) { GenerateRandomLegalPosition(&pos); - FOREACH_SQUARE(c) + FOREACH_SQUARE(c) { if (!IS_ON_BOARD(c)) continue; - for (color = BLACK; color <= WHITE; color++) + for (color = BLACK; color <= WHITE; color++) { SlowGetAttacks(&rgSlowList, &pos, @@ -185,11 +206,104 @@ TestGetAttacks(void) if (!SeeListsAreEqual(&rgSlowList, &rgAsmList)) { UtilPanic(TESTCASE_FAILURE, - &pos, + &pos, "SEE_LIST mismatch", &rgSlowList, &rgAsmList, __FILE__, __LINE__); } + + // board_representation/MIGRATION.md section 3/4: + // bbPieces-backed GetAttacks PoC, same correctness + // gate as the asm/C comparison above. + _GetAttacksBB(&rgBBList, + &pos, + c, + color); + if (!SeeListsAreEqual(&rgSlowList, &rgBBList)) + { + UtilPanic(TESTCASE_FAILURE, + &pos, + "SEE_LIST mismatch (_GetAttacksBB)", + &rgSlowList, &rgBBList, + __FILE__, __LINE__); + } + } + } + } + + // + // Speed: board_representation/MIGRATION.md section 5's isolated + // cycles/call microbenchmark, pulled forward here since it's cheap + // to add right alongside the correctness gate that just proved the + // two implementations equivalent. Three positions spanning piece + // density (opening/middlegame/endgame), SlowGetAttacks vs + // _GetAttacksBB interleaved call-by-call (not phase-by-phase) to + // cancel shared-box noise -- a red flag (flat or inverted result) + // here would mean stopping before wiring this in any further, same + // as the Eval occupancy-bitboard work that motivated this file. + { + 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 u64SlowTotal, u64AsmTotal, u64BBTotal, u64Start; + ULONG uIter; + ULONG uSq; + COOR cBench; + ULONG uSide; + const ULONG uCallsPerPosition = 200000; + + // GetAttacks (unqualified) is the real production entry point -- + // the hand-tuned x86/x64 asm routine, not SlowGetAttacks (the C + // reference used only for correctness comparison above). That's + // the actual competitor _GetAttacksBB has to beat; SlowGetAttacks + // is included only as a third data point, not the bar to clear. + Trace("Benchmarking GetAttacks: asm GetAttacks vs SlowGetAttacks " + "vs _GetAttacksBB (interleaved, %lu calls/position)...\n", + uCallsPerPosition); + for (u = 0; u < 3; u++) + { + FenToPosition(&posBench, (char *)rgszFen[u]); + u64SlowTotal = 0; + u64AsmTotal = 0; + 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(); + GetAttacks(&rgList, &posBench, cBench, uSide); + u64AsmTotal += (SystemReadTimeStampCounter() - u64Start); + + u64Start = SystemReadTimeStampCounter(); + SlowGetAttacks(&rgList, &posBench, cBench, uSide); + u64SlowTotal += (SystemReadTimeStampCounter() - u64Start); + + u64Start = SystemReadTimeStampCounter(); + _GetAttacksBB(&rgList, &posBench, cBench, uSide); + u64BBTotal += (SystemReadTimeStampCounter() - u64Start); } + printf(" %s: asm GetAttacks %" COMPILER_LONGLONG_UNSIGNED_FORMAT + " cycles/call, SlowGetAttacks %" + COMPILER_LONGLONG_UNSIGNED_FORMAT + " cycles/call, _GetAttacksBB %" + COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call " + "(BB is %.2fx asm)\n", + rgszLabel[u], + u64AsmTotal / uCallsPerPosition, + u64SlowTotal / uCallsPerPosition, + u64BBTotal / uCallsPerPosition, + (double)u64BBTotal / (double)u64AsmTotal); } } } |
