Repository navigation
Two phase move loop, the probe's entry handed back, and the first search asked from the loop - #403
Conversation
…e penalty from an array A probe wrote the entry it found into a cell on the table, and a full width node read its static evaluation and its floor back from the cell straight after. Every probe paid for the write, quiescence's included, which reads nothing back. A hit also built the entry on the stack to have something to write, and since the write went through a `Cell` the compiler reloaded what it had read through `&self` around it. The probe now returns the entry beside its answer, as `Found`, and the node reads the evaluation and the floor from that. Quiescence drops it. The cell goes, and with it the debug check that no other probe came between the probe and the read: the entry now travels with the node that asked for it. The store's contest took each entry's age modulo the 31 generations and multiplied it by the age weight. The table now keeps the weight times each generation's age in a 32 slot array beside the generation, rewritten wherever the generation or the weight changes, and the contest reads it. The debug build checks it against the formula at every call. Every probe answers and every store lands as before. Node counts and every bench line are unchanged. Callgrind against the parent: the games suite's tree search goes from 11,050,460,280 instructions to 10,847,099,545 (-1.840%), the bench's from 16,254,520,835 to 15,987,709,636 (-1.641%). Measured apart on the parent's parent, which has the same search code, the entry handed back is 1.628 points of the games suite and the penalty array 0.212. Bench: 11088536 Speed: +2.4% (bench nps, 95% interval +0.4% to +4.6%, 60 interleaved rounds over shuffled layouts vs b48777a) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtepPHhwporfDvjPSnX4ae
…rched move, in a two phase move loop A full width node built the node halves of the late move reduction and of quiet futility and move count pruning (`Rules::new`) before its move loop, and the compiler worked out the loop's invariants for the gate on entry. No rule acts on the first move a node searches, and most loops never get past it: on the games suite the rules are now built at 808,818 of 1,823,170 loops (counted by an assertion build). The loop now runs in two phases when the table's move was not searched. The first takes the places up to the first move searched, asks no rule, and stops after that move; a cutoff returns as before. Only a node with a place left builds the rules and enters the second phase, which is the old loop less its test for the first move. Three smaller changes go with it: - The loop reads the move list through a slice, so an index no longer asks whether the list spilled its inline buffer. - The quiet ordering keeps the first place it has work at (`QuietOrder::at`), so a place outside the quiet run asks one compare in place of three tests. - The two rule halves hold the mate test on alpha, asked again when the loop raises alpha (`Rules::raise`), rather than asking it at every move. This puts back a value kept in step with alpha, which ba83869 removed. The debug build checks the held answer against the test at every call. Before the first searched move the searched count is zero, so no rule could have acted there. The rules read the node's facts, the position and the evaluation, none of which the first search changes, and alpha, which the held test reads when the rules are built and at every rise. Node counts and every bench line are unchanged. Callgrind against the parent: the games suite's tree search goes from 10,847,099,545 instructions to 10,525,445,589 (2.911 points of the tree search before the series), the bench's from 15,987,709,636 to 15,600,283,382 (2.383 points). `alpha_beta` shrinks from 15,812 bytes to 13,713, with the quiet keying (1,571 bytes) and `Rules::new` (364) now out of line. Bench: 11088536 Speed: +0.3% (bench nps, 95% interval -1.1% to +1.8%, 60 interleaved rounds over shuffled layouts vs 3a8a067) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtepPHhwporfDvjPSnX4ae
…h zero child to a leaf entry Every searched move went through `windowed`, an out of line function between the node and its child that asked the child's searches: on the games suite 5,925,401 calls at 56.7 instructions of its own each, for what is one search unless a later move rises above alpha. A child at depth zero out of check then entered `alpha_beta` only to run its entry tests and hand the position to quiescence, which repeated part of them. `search_child` now asks principal variation search's first search itself. The rest of the sequence (the probe after a scout that rose above alpha, and the proof at an open window) is the cold `windowed_rest`. `windowed` stays whole for a move whose scout an instrument staged, so the ledger and the forced decision instrument see the calls they saw; a debug assertion holds that a reduced move reaching the new path has no instrument armed. A child at depth zero out of check goes to `leaf`, a small out of line entry that runs `alpha_beta`'s entry for such a node in its order (the poll, the node count, the fifty move rule and repetition, the rail, mate distance) and then the quiescence body. Quiescence is split into an inlined head and that one body, so there is still one copy of it. The null move's pass goes through the same dispatch. The leaf writes out `answered_by_rule` with the check known false, and comments on both and on `alpha_beta`'s depth zero exit say a rule added to one goes to the other. A new test asks `alpha_beta` at depth zero and the leaf the same nodes on two engines (every bench and games suite position out of check, a fifty move draw and a repetition, at four line plies and seven windows, two of which mate distance narrows or closes) and holds the values, taints, node counts and selective depth equal. Another asks `search_child` and `windowed` the same children under the loop's three decisions, at positions two or three plies short of the fifty move rule, and holds the values, the taints `windowed_rest` carries and the node counts equal. Every node is searched with the window, depth and bounds it had. Node counts and every bench line are unchanged. Callgrind against the parent: the games suite's tree search goes from 10,525,445,589 instructions to 10,231,570,885 (2.659 points of the tree search before the series), the bench's from 15,600,283,382 to 15,285,054,673 (1.939 points). The code on the per child path grows by about 425 bytes, with `windowed` (705) and `windowed_rest` (214) now off it, and a leaf touches 624 bytes of stack where the parent's call into `windowed`, `alpha_beta` and quiescence touched 2,184. Bench: 11088536 Speed: +1.1% (bench nps, 95% interval -1.3% to +3.5%, 60 interleaved rounds over shuffled layouts vs 4d4741e) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtepPHhwporfDvjPSnX4ae
Speed against
|
Three changes to the search node's own work. Each is exact: every node
is searched with the same window, every probe answers and every store
lands as before, and node counts and every bench line are unchanged
(11,088,536).
Measured on d82638a, the base's parent, which has the same search
code, an interleaved clock over shuffled layouts read the games suite
at 16 MB +4.96% (+3.56% to +6.37%, 200 rounds) and at 256 MB +3.67%
(+2.17% to +5.18%, 60 rounds). Each commit was faster than the one
before it on the 16 MB clock. The same clock read the bench +4.76%
(+3.40% to +6.21%, 100 rounds).
scripts/speed.shreads the headagainst b48777a at +2.6% (+1.3% to +3.9%, 120 rounds), and the commits
at +2.4%, +0.3% and +1.1% over 60 rounds each.
writing it to a cell on the table for the node to read back. The
store's contest reads the age penalty from a 32 slot array kept
beside the generation.
move reduction and of the shallow rules are built only once a node
has searched a move and has a place left. The loop reads the list
through a slice, the quiet ordering keeps one place threshold, and
the rules hold the mate test on alpha.
search_childasks principal variation search's first searchitself, with the rest of the sequence in a cold
windowed_rest, anda child at depth zero out of check goes to a small leaf entry in
front of the one quiescence body.
Callgrind against b48777a, the games suite's tree search: 11,050,460,280
instructions to 10,231,570,885 (-7.410%); the bench's 16,254,520,835 to
15,285,054,673 (-5.964%). By commit, in points of the base: -1.840,
-2.911 and -2.659 on the games suite, -1.641, -2.383 and -1.939 on the
bench.
Constraints this leaves:
Rulesholds the mate test on alpha. Alpha moves only at the loop'sone rise, which calls
Rules::raise; an edit that moves alphaanywhere else in the loop has to call it too. The debug build checks
the held answer at every call.
node's first searched move would have to move into the first phase.
leafwrites outanswered_by_ruleandalpha_beta's depth zeroentry, and must keep in step with them. Comments on all three say so,
and a test asks
alpha_betaand the leaf the same nodes.windowednow runs whole only for a move whose scout the ledger orthe forced decision instrument staged. A test holds
search_childand
windowedto the same values and taints.weight changes (
with_capacity,clear,new_search). A new placethat writes either has to rewrite it; the debug build checks it
against the formula at every contest.
🤖 Generated with Claude Code
https://claude.ai/code/session_01DtepPHhwporfDvjPSnX4ae
Generated by Claude Code