diff options
| author | Scott Gasch <[email protected]> | 2026-08-24 19:31:36 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-08-24 19:31:36 -0700 |
| commit | 29d73f4dd59554a349aa8e86e5ea65f28c912ec9 (patch) | |
| tree | 05e8dcbe6eb3c2f6d8bfd7c8065c250b2734254c /src/command.c | |
| parent | c5ca1a37b0614f5cb55499692a7f61775f81d1eb (diff) | |
Started doing texel eval tuning.
Diffstat (limited to 'src/command.c')
| -rwxr-xr-x | src/command.c | 191 |
1 files changed, 182 insertions, 9 deletions
diff --git a/src/command.c b/src/command.c index d528994..4f81b10 100755 --- a/src/command.c +++ b/src/command.c @@ -32,6 +32,11 @@ Revision History: #define MAX_ARGS 32 int g_eModeBeforeAnalyze = -1; +// Bumped by EvalDnaCommand on every successful `evaldna read`/`load`; +// EvalCommand compares against this to know its persistent eval-hash +// cache needs clearing (see EvalCommand for details). +ULONG g_uDnaGeneration = 0; + typedef void (COMMAND_PARSER_FUNCTION)(CHAR *szInput, ULONG argc, CHAR *argv[], @@ -497,19 +502,175 @@ Return value: **/ { - SEARCHER_THREAD_CONTEXT *ctx; + // rgPawnHash/rgEvalHash are embedded arrays inside + // SEARCHER_THREAD_CONTEXT (tens of MB), so allocating fresh + + // InitializeSearcherContext's full memset on every call here was + // paying that cost per invocation. Keep one persistent context + // for the life of the process and use the cheap + // ReInitializeSearcherContext (memmove position + zero counters + // only) on every call after the first, same pattern the real + // search path uses across moves. + // + // CORRECTNESS: ReInitializeSearcherContext does NOT clear + // ctx->rgEvalHash, so a stale score cached under previously-loaded + // DNA constants would otherwise be returned forever for any + // position hit more than once -- silently ignoring every + // subsequent `evaldna read`. g_uDnaGeneration (bumped by + // EvalDnaCommand on a successful load) is compared here so a DNA + // change forces exactly one full re-init (i.e. one eval-hash + // clear per reload, not per position) instead of a stale hit. + static SEARCHER_THREAD_CONTEXT *ctx = NULL; + static ULONG uLastSeenDnaGeneration = (ULONG)-1; SCORE i; - ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT)); - if (NULL != ctx) + if (NULL == ctx) { + ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT)); + if (NULL == ctx) + { + Trace("Out of memory.\n"); + return; + } InitializeSearcherContext(pos, ctx); - i = Eval(ctx, -INFINITY, +INFINITY); - Trace("Static eval: %s\n", ScoreToString(i)); - SystemFreeMemory(ctx); - } else { - Trace("Out of memory.\n"); + uLastSeenDnaGeneration = g_uDnaGeneration; } + else if (uLastSeenDnaGeneration != g_uDnaGeneration) + { + InitializeSearcherContext(pos, ctx); + uLastSeenDnaGeneration = g_uDnaGeneration; + } + else + { + ReInitializeSearcherContext(pos, ctx); + } + i = Eval(ctx, -INFINITY, +INFINITY); + Trace("Static eval: %s\n", ScoreToString(i)); +} + + +COMMAND(QSearchCommand) +/** + +Routine description: + + This function implements the 'qsearch' engine command. + + Usage: + + qsearch + + This command takes no arguments and causes the engine to run + a quiescence search on the current board position and print + the resulting score. Unlike `eval` (a static evaluation with + no search at all), this resolves captures/checks/promotions + first, the same way the real search does immediately before + calling Eval() -- so it's a much better proxy for "is this + position actually quiet" than comparing SEE-based capture + analysis to a static score. + +Parameters: + + The COMMAND macro hides four arguments from the input parser: + + CHAR *szInput : the full line of input + ULONG argc : number of argument chunks + CHAR *argv[] : array of ptrs to each argument chunk + POSITION *pos : a POSITION pointer to operate on + +Return value: + + void + +**/ +{ + // Same persistent-context + DNA-generation-invalidation pattern as + // EvalCommand (see its comment for the full rationale) -- this + // command is meant to be called at corpus scale from the eval + // tuner, so paying a fresh ~75MB memset per call would be the + // same 1000x throughput hit that command had before it was fixed. + static SEARCHER_THREAD_CONTEXT *ctx = NULL; + static ULONG uLastSeenDnaGeneration = (ULONG)-1; + CUMULATIVE_SEARCH_FLAGS *pf; + PLY_INFO *pi; + SCORE i; + + if (NULL == ctx) + { + ctx = SystemAllocateMemory(sizeof(SEARCHER_THREAD_CONTEXT)); + if (NULL == ctx) + { + Trace("Out of memory.\n"); + return; + } + InitializeSearcherContext(pos, ctx); + uLastSeenDnaGeneration = g_uDnaGeneration; + } + else if (uLastSeenDnaGeneration != g_uDnaGeneration) + { + InitializeSearcherContext(pos, ctx); + uLastSeenDnaGeneration = g_uDnaGeneration; + } + else + { + ReInitializeSearcherContext(pos, ctx); + } + + // QSearch is normally only ever entered from Search() one ply + // below the position it's evaluating (see search.c's "the only + // place Qsearch is entered" comment) -- it reads (pi-1)->mv to + // learn whether the side to move just got checked into this + // position, and asserts ctx->uPly > 0. To call it directly on + // the root position, synthesize that one piece of ply-0 state: + // a fake "previous move" whose only job is to carry the correct + // checking-move flag, so QSearch's in-check handling matches the + // position's actual, real InCheck() status instead of silently + // taking the no-standpat path incorrectly for a position that IS + // in check (or vice versa). + ctx->uPly = 1; + memset(&ctx->sPlyInfo[0].mv, 0, sizeof(MOVE)); + if (TRUE == InCheck(&ctx->sPosition, ctx->sPosition.uToMove)) + { + ctx->sPlyInfo[0].mv.uMove |= 0x80000000; + } + + pf = &ctx->sSearchFlags; + pi = &ctx->sPlyInfo[ctx->uPly]; + pf->fCouldStandPat[BLACK] = pf->fCouldStandPat[WHITE] = FALSE; + pf->uQsearchNodes = pf->uQsearchDepth = 0; + pf->uQsearchCheckDepth = QPLIES_OF_NON_CAPTURE_CHECKS; + pi->fInQsearch = TRUE; + + // QSearch's recursive move loop checks WE_SHOULD_STOP_SEARCHING, + // which reads the process-global g_MoveTimer -- normally set up + // by SetMoveTimerForSearch() at the start of every real search. + // This command is a standalone call outside that path, so without + // this g_MoveTimer is whatever state a prior real search left it + // in (e.g. an already-expired hard limit). Save/restore around + // the call so a `qsearch` invoked between real searches (e.g. + // interactive use after `go`) doesn't leave the timer in a + // "think forever" state for whatever search comes next. + // + // g_uHardExtendLimit is a second process-global with the same + // problem, and the one actually responsible for a bogus +MATE + // score on every call before this fix: CommonSearchInit's + // "uQsearchDepth >= g_uHardExtendLimit" too-deep check (see its + // comment) fires immediately when this is left at its zero- + // initialized default, since it's normally only ever set once + // per real search, inside RootSearch (g_uIterateDepth * 4) -- + // a path this standalone command never runs through. Set it + // generously (MAX_PLY_PER_SEARCH; qsearch's own capture-only move + // generation makes runaway recursion a non-issue) for the + // duration of the call, same save/restore reasoning as the timer. + { + MOVE_TIMER SavedMoveTimer = g_MoveTimer; + ULONG uSavedHardExtendLimit = g_uHardExtendLimit; + SetMoveTimerToThinkForever(); + g_uHardExtendLimit = MAX_PLY_PER_SEARCH; + i = QSearch(ctx, -INFINITY, +INFINITY); + g_MoveTimer = SavedMoveTimer; + g_uHardExtendLimit = uSavedHardExtendLimit; + } + Trace("Qsearch score: %s\n", ScoreToString(i)); } @@ -568,10 +729,16 @@ Return value: Trace("Error (missing argument)\n"); return; } - if (!ReadEvalDNA(argv[2])) + if (!ReadEvalDNA(argv[2])) { Trace("Error reading dna file.\n"); } else { + // EvalCommand's persistent SEARCHER_THREAD_CONTEXT keeps + // its embedded eval hash alive across calls for speed; a + // DNA reload must invalidate stale cached scores from the + // old constants, which this generation bump signals it + // to do (see EvalCommand for the actual clear). + g_uDnaGeneration++; Trace("Loaded dna file \"%s\"\n", argv[2]); p = ExportEvalDNA(); Log("(New) dna: %s\n", p); @@ -2188,6 +2355,12 @@ COMMAND_PARSER_ENTRY g_ParserTable[] = TRUE, FALSE, "Learn a set of PSQT settings from a PGN file" }, + { "qsearch", + QSearchCommand, + FALSE, + FALSE, + FALSE, + "Run a quiescence search on the current position" }, { "quit", QuitCommand, TRUE, |
