diff options
| author | Scott Gasch <[email protected]> | 2026-10-13 23:22:10 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-13 23:22:10 -0700 |
| commit | dad4620a92f51a1d0825fc99fa98fd2b16c0f280 (patch) | |
| tree | 865a0f210b6d3ad49779617ba73a3e04c02aad4e /src/searchsup.c | |
| parent | 7400001fc2d43a507b54281f9860c60177860fe5 (diff) | |
Add alpha-side static null-move skip gate and Search() perf hoists
Reuses the joint FH% table on the iEval <= iAlpha side to skip the
null-move probe attempt when it's predicted unlikely to succeed
(EV-neutral: the real move loop still runs regardless). Also hoists
several node-constant computations (ImproveBucket, MP split-eligibility
checks) out of the per-move loop in Search(), and adds GNUmakefile
overrides for sweeping NULLMOVE_SKIP_FH_CEILING_THOUSANDTHS and
SKIP_SELECT_AFTER_N_MOVES from the command line.
Self-play vs. head is trending positive overnight (1020 games,
llr=+1.36, score=0.527) but not yet conclusive -- committing now to
checkpoint the change while the match continues.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/searchsup.c')
| -rw-r--r-- | src/searchsup.c | 85 |
1 files changed, 63 insertions, 22 deletions
diff --git a/src/searchsup.c b/src/searchsup.c index 66851a5..53e7a85 100644 --- a/src/searchsup.c +++ b/src/searchsup.c @@ -503,6 +503,7 @@ GapBetaBucket(SCORE iGapBeta) return MINU(u, GAP_BETA_BUCKETS - 1); } + // See chess.h's MOVE_NUM_BUCKETS comment. uMoveNum is 1-based (the // caller passes uLegalMoves, right after MakeMove succeeded) -- index // 0 is never produced. @@ -1040,10 +1041,24 @@ Return value: return(iLocalExtend); } -#define LMP_BASE_MOVENUM (8) // never prune before this many - // legal moves already tried -#define LMP_PER_PLY_MOVENUM (1) // + this many moves of extra - // slack per remaining ply +// 2026-10-13: LMP_BASE_MOVENUM/LMP_PER_PLY_MOVENUM/LMP_MAX_REMAINING_DEPTH +// are NOT wired into ShouldLatePrune below -- there is no move-number +// floor and no depth ceiling in this function today, despite this +// comment block and the docstring below once describing four checks. +// uLegalMoves is still a parameter (kept for signature/call-site +// stability and because MoveNumBucket already folds move number into +// uPredictedFH upstream, in search.c, before this function ever sees +// it) but it is unread here. LMP_MAX_REMAINING_DEPTH never existed as +// code, only as a comment; LMP_BASE_MOVENUM/LMP_PER_PLY_MOVENUM are +// defined and then never referenced -- dead macros, not a disabled +// feature flag. Left defined rather than deleted only because a +// future re-introduction of an explicit floor/ceiling (e.g. if the +// table's move-number axis turns out too coarse near the root once +// EFP is folded into this function -- see the EFP/LMP merge +// discussion) would want exactly these names back. If that doesn't +// happen, delete them. +#define LMP_BASE_MOVENUM (8) // currently unused, see above +#define LMP_PER_PLY_MOVENUM (1) // currently unused, see above #define LMP_DYNAMIC_MIN_SAMPLES (1) // well-evidenced, not a guess #define LMP_DYNAMIC_MAX_PCT_THOUSANDTHS (1) @@ -1068,33 +1083,59 @@ Routine description: rather than recomputing them -- search.c's move loop already looks them up once per move for LMP/LMR/EFP to share. - Four remaining checks, any one of which refuses to prune: - 1. Depth ceiling (LMP_MAX_REMAINING_DEPTH) -- see the KNOWN ISSUE - comment above this function: backwards relative to our own - joint-table evidence, kept as a placeholder pending a per-cell - sample-count re-bake, not a validated bound. - 2. Move-number floor (LMP_BASE_MOVENUM + per-ply slack) -- don't - gamble this early in the move list regardless of what either - FH signal says. - 3. Dynamic signal: any real, nonzero observed fail-high history - on this exact move identity. - 4. Static signal: the joint table's own prediction for this - move's situation, outside the strictest (safest) tier, or no - data at all (sentinel) -- unlike GetLMRReduction's more - permissive treatment of a sentinel for mere reduction, overt - pruning requires real, favorable data. + 2026-10-13: corrected from an earlier version of this comment that + claimed four checks including a move-number floor and a depth + ceiling -- neither exists in the body below (see the dead-macro + note above). As actually implemented, there are only two checks, + either of which refuses to prune: + 1. Dynamic signal: real, well-evidenced fail-high history on + this exact move identity that isn't essentially zero -- the + ultra-safe tail, not "probably fine." + 2. Static signal: the joint table's own prediction + (uPredictedFH) for this move's (depth, hang, gap, late, + improve) situation, checked against a per-remaining-depth + threshold (uLMPStaticMaxPctThousandths[]) that tightens as + remaining depth grows -- unlike GetLMRReduction's more + permissive treatment of missing data for mere reduction, + overt pruning requires a favorable prediction. + There is accordingly no explicit guard today against pruning the + 8th move at the root the instant the table happens to look + favorable, nor any floor stopping this from firing arbitrarily + deep in the tree -- both are purely emergent from + uLMPStaticMaxPctThousandths[]'s own shape (very permissive near + remaining depth 0, essentially 0 by remaining depth 10) rather + than from any dedicated gate. If that turns out to be too loose in + practice (e.g. once EFP's margin-based alpha-side pruning is + folded into this same table-driven path and starts operating at + the 1-2-ply horizon EFP currently owns), re-introduce an explicit + floor/ceiling using the macros above rather than reshaping the + static-threshold array to fake one. + + The uPredictedFH == JOINT_TABLE_SENTINEL check below is currently + unreachable in practice: g_uJointFHThousandths (data.c) is fully + backoff-smoothed (shrinkage to a coarser marginal rather than a + missing-data sentinel) as of the splice-dual bake that added the + improve axis -- "No 0xffff sentinels remain" per that table's own + header comment. Kept as a defensive check (cheap, and correct even + if the table is ever rebaked with real gaps again), not because + it's expected to fire today. Parameters: ctx : current searcher thread context (uPly only, for the sanity ASSERT). uRemainingDepth : remaining depth below this move. - uLegalMoves : legal moves tried at this node so far. + uLegalMoves : legal moves tried at this node so far -- + currently unused by this function (see the + dead-macro note above); kept in the + signature for call-site stability. uFHAttempts : this move identity's dynamic sample count. uFHPctThousandths : this move identity's dynamic fail-high rate. uPredictedFH : the joint table's static prediction for this - move's (depth, hang, gap, late) situation, or - JOINT_TABLE_SENTINEL if no data. + move's (depth, hang, gap, late, improve) + situation, or JOINT_TABLE_SENTINEL if no data + (see note above on why this is effectively + dead today). Return value: |
