summaryrefslogtreecommitdiff
path: root/src/testsee.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testsee.c')
-rw-r--r--src/testsee.c136
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);
}
}
}