summaryrefslogtreecommitdiff
path: root/src/testsee.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-07 08:17:51 -0700
committerScott Gasch <[email protected]>2026-10-07 08:17:51 -0700
commitd14dbe2a50c220b2adac758c3d4f1a1fa67ece0b (patch)
treecbf6d9928bcc41ef833b62b70479113f7d419925 /src/testsee.c
parent447130db3df87b40d080dc26429d45e9213c19f7 (diff)
Speed up eval/move-gen/SEE; remove EvalLookForDanger, overhaul _EvalKing
SPRT-confirmed improvement (1814 games, st 4.0): candidate nps=1498118 vs head=1378448, avg depth 14.30 vs 13.87, ELO=+24.2+/-16.0, LOWER95=0.5118, LLR=+3.126 crossing the H1 bound. Most of the diff is non-semantic speed work across eval.c, generate.c, movesup.c, see.c, probe.c and friends. Two real semantic changes: removed EvalLookForDanger and overhauled _EvalKing's king-safety scoring. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/testsee.c')
-rw-r--r--src/testsee.c155
1 files changed, 155 insertions, 0 deletions
diff --git a/src/testsee.c b/src/testsee.c
index bbcd7e0..240ac39 100644
--- a/src/testsee.c
+++ b/src/testsee.c
@@ -624,4 +624,159 @@ TestWhoAttacksSquareMagic(void)
}
}
}
+
+//
+// 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