Fast chess move generator
- Zig 97.6%
- Python 2.2%
- Nix 0.2%
|
|
||
|---|---|---|
| src | counts "leaves" not "nodes" | |
| test | Init | |
| .envrc | Init | |
| .gitignore | Update .gitignore | |
| build.zig | add profile build flag | |
| build.zig.zon | Init | |
| flake.lock | bump lock | |
| flake.nix | clean up flake.nix | |
| LICENSE | Add license and readme | |
| README.md | update readme | |
Perft Benchmarks
Hardware: AMD Ryzen 7 7840U (8 cores, Zen 4)
OS: Linux (TTY mode, no GUI running)
Single-threaded (bulk counted, no transposition table)
Starting position
| Depth | Leaves | Time | MLPS | Command |
|---|---|---|---|---|
| 5 | 4,865,609 | 0.002s | 2355.37 | ./perft 5 |
| 6 | 119,060,324 | 0.047s | 2,524.73 | ./perft 6 |
| 7 | 3,195,901,860 | 1.164s | 2,746.79 | ./perft 7 |
Kiwipete position
| Depth | Leaves | Time | MLPS | Command |
|---|---|---|---|---|
| 4 | 4,085,603 | 0.001s | 2728.54 | ./perft 4 pos 2 |
| 5 | 193,690,690 | 0.045s | 4,313.19 | ./perft 5 pos 2 |
| 6 | 8,031,647,685 | 2.044s | 3,928.59 | ./perft 6 pos 2 |
Multi-threaded (bulk counted, transposition table)
Starting position
| Depth | Leaves | Time | MLPS | Command |
|---|---|---|---|---|
| 7 | 3,195,901,860 | 0.143s | 22,325.79 | ./perft 3 1x2 2 |
| 8 | 84,998,978,956 | 0.898s | 94,703.28 | ./perft 3 1x2 3 |
| 9 | 2,439,530,234,167 | 12.486s | 195,388.31 | ./perft 3 1x3 3 |
| 10 | 69,352,859,712,417 | 184.020s | 376,876.88 | ./perft 3 1x4 3 |
Kiwipete position
| Depth | Leaves | Time | MLPS | Command |
|---|---|---|---|---|
| 6 | 8,031,647,685 | 0.278s | 28,839.18 | ./perft 3 1 2 pos 2 |
| 7 | 374,190,009,323 | 4.863s | 76,948.95 | ./perft 3 1 3 pos 2 |
| 8 | 15,493,944,087,984 | 115.987s | 133,583.46 | ./perft 3 1x2 3 pos 2 |
MLPS = millions of leaves per second.
Performance Notes
Move generation
- PEXT-based lookup tables for both attacks and xrays.
- Visitor pattern Counts are accumulated in place rather than written into a move list and then read back.
- Branches for fast paths, not branchless. Large branches gate fast paths for the common cases of no checks and no pins, which is much faster than a uniform branchless path.
Code generation
- Heavy templating and aggressive inlining
- Height-2 specialization. Perft is specialized only at heights 1 and 2. Above height 2 it is plain recursion on depth. This causes the bulk-counting height-1 nodes to be inlined into the height-2 nodes, which significantly inflates compile time and binary size but increases performance. Specializing larger heights is likely pure bloat with no measurable benefit.
Memory layout
- Full board state fits in two cache lines.
- Compressed board state fits in half a cache line (32 bytes).
Multi-threaded deduplication (no hash table)
The multi-threaded mode does not use a hash table for its transposition table. Instead it deduplicates positions (with multiplicity) using external hashed merge sort of compressed states, then runs the final leaf count on the deduplicated set.
- Staged deduplication. Several dedup stages are layered. For example,
perft 10deduplicates down to depth 7 using 5 deduplication stages and then runsperft 3on each deduplicated position. - Correctness is independent of hash quality. Deduplication compares full keys, so hash collisions cannot affect the result.
Compilation Instructions
Build with Zig 15.2:
zig build --release=fast
AI Disclosure
Core code is entirely and carefully handwritten. AI partially wrote peripheral parts: tests, CLI frontend, debugging tools, and this README.