Skip to content

feat(search): shared transposition table + Lazy SMP - #8

Merged
cjunius merged 1 commit into
mainfrom
feat/transposition-table-and-lazy-smp
Aug 30, 2026
Merged

cjunius merged 1 commit into
mainfrom
feat/transposition-table-and-lazy-smp

Conversation

@cjunius

@cjunius cjunius commented Aug 30, 2026

Copy link
Copy Markdown
Owner

What

Two search features from the roadmap, both leaning on the Zobrist hash dragontoothmg already exposes (Board.Hash()):

Transposition table (internal/engine/transposition.go)

  • Fixed-size, power-of-two table; size set by the new UCI Hash option (default 64 MiB).
  • 16-byte slots, each a pair of atomic.Uint64 words accessed with Hyatt's lockless XOR trick (word0 = key ^ data) — safe for concurrent Lazy-SMP access with no mutex; a write torn across goroutines simply reads as a miss.
  • data packs best move, an int32 score, depth, and an EXACT / LOWER / UPPER bound flag.
  • Mate scores are rebased by ply on store and probe so "mate in N from the root" survives a table round-trip.
  • negamax probes for a cut-off and stores its result with the correct bound; the stored move is ordered first in orderMoves even when the entry is too shallow to cut.

Lazy SMP (internal/engine/search.go)

  • SearchParams.Threads workers run iterative deepening in parallel, each on its own copy of the board (dragontoothmg.Board is all value types), over the one shared table.
  • Workers start at staggered depths so TT races push them into different subtrees; the deepest completed result wins.
  • The UCI layer owns a persistent engine.TT (cleared on ucinewgame) and honours a new Threads option, capped at runtime.NumCPU() at search time.

Modelled on the same design in pyChess (lazy_smp.py / shared_tt.py), adapted to Go's shared-memory goroutines instead of multiprocessing.

Impact (Apple M4, depth from the opening)

depth before with TT (1 thread)
7 3.4s / 23.6M nodes 0.5s / ~3.1M nodes
8 30s / 161M nodes 1.3s / ~5.9M nodes

Basic Lazy SMP (no root-move splitting yet) adds parallel breadth and tactical robustness; fixed-depth wall-clock is roughly flat pending root splitting, which is noted in the roadmap and docs.

Tests

  • transposition_test.go — bound/window semantics, depth-preferring replacement, ply rebasing round-trip, and a -race concurrent-access test.
  • search_test.go — TT does not change the best move, warm TT cuts node count, and the Lazy-SMP path finds mate / wins material / respects movetime.
  • go test -race ./..., golangci-lint (v2.13.2), govulncheck, gofumpt all clean.

🤖 Generated with Claude Code

Add a fixed-size, power-of-two transposition table keyed by the Zobrist
hash dragontoothmg maintains incrementally (Board.Hash). Each 16-byte
slot is a pair of atomic.Uint64 words accessed with Hyatt's lockless XOR
trick, so it is safe for concurrent use with no mutex. Entries store the
best move plus an EXACT / LOWER / UPPER bound; mate scores are rebased by
ply on store and probe.

negamax now probes the table for a cut-off and stores its result, and the
stored move is ordered first in orderMoves even when the entry is too
shallow to cut. This alone drops a depth-8 search from the opening from
~30s to ~1.3s single-threaded.

Add Lazy SMP: SearchParams.Threads workers run iterative deepening in
parallel on private board copies over the one shared table, seeded at
staggered start depths so they diverge; the deepest completed result
wins. The UCI layer owns a persistent engine.TT (sized by the new Hash
option, cleared on ucinewgame) and honours a new Threads option, capped
at the machine's core count.

Docs (README, architecture, design, engine-strength, performance) updated
to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 79.06977% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/uci/uci.go 35.89% 25 Missing ⚠️
internal/engine/transposition.go 88.67% 6 Missing ⚠️
internal/engine/search.go 93.75% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@cjunius
cjunius merged commit d6045aa into main Aug 30, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants