Skip to content

[1/6] Improve PSQT evaluation performance#31

Open
luccabb wants to merge 2 commits intomasterfrom
feature/psqt-evaluation-performance
Open

[1/6] Improve PSQT evaluation performance#31
luccabb wants to merge 2 commits intomasterfrom
feature/psqt-evaluation-performance

Conversation

@luccabb
Copy link
Owner

@luccabb luccabb commented Jan 20, 2026

Summary

  • Use Zobrist hash instead of FEN string for evaluation cache keys (faster integer hashing vs string comparison)
  • Add cache size limit (1M entries) to prevent unbounded memory growth during long searches
  • Optimize board_evaluation() to iterate over actual pieces (~16-32) instead of all 64 squares

Details

The board evaluation function is called frequently during search. These optimizations reduce overhead:

  1. Zobrist hashing: chess.polyglot.zobrist_hash() returns an integer, which is faster to hash and compare than FEN strings
  2. Cache eviction: Simple clear-on-full policy prevents memory exhaustion in long games
  3. Piece iteration: Using board.pieces(piece_type, color) returns only occupied squares, reducing iterations by 50-75%

Test plan

  • All 64 unit tests pass
  • No changes to search behavior, only evaluation speed

🤖 Generated with Claude Code

@luccabb luccabb force-pushed the feature/psqt-evaluation-performance branch from bca3f3e to d7df55a Compare January 21, 2026 06:43
@luccabb luccabb changed the title [1/9] Improve PSQT evaluation performance [1/7] Improve PSQT evaluation performance Jan 21, 2026
@luccabb luccabb force-pushed the feature/psqt-evaluation-performance branch from d7df55a to e21928a Compare January 21, 2026 07:33
@luccabb luccabb changed the title [1/7] Improve PSQT evaluation performance [1/6] Improve PSQT evaluation performance Jan 21, 2026
@github-actions
Copy link

🔬 Stockfish Benchmark Results

vs Stockfish Skill Level 3

Metric Wins Losses Draws Total Win %
Overall 32 60 8 100 32.0%
As White 15 31 4 50 30.0%
As Black 17 29 4 50 34.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 6

vs Stockfish Skill Level 4

Metric Wins Losses Draws Total Win %
Overall 25 70 5 100 25.0%
As White 11 37 2 50 22.0%
As Black 14 33 3 50 28.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 5

vs Stockfish Skill Level 5

Metric Wins Losses Draws Total Win %
Overall 9 83 8 100 9.0%
As White 6 39 5 50 12.0%
As Black 3 44 3 50 6.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 5
Configuration
  • 5 chunks × 20 rounds × 3 skill levels = 300 total games
  • Each opening played with colors reversed (-repeat) for fairness
  • Moonfish: 60s per move
  • Stockfish: 60+5 time control

@greptile-apps
Copy link

greptile-apps bot commented Feb 2, 2026

Greptile Overview

Greptile Summary

This PR optimizes the board evaluation performance through three key improvements:

  • Replaced FEN string-based cache keys with Zobrist hash integers for faster lookups
  • Added 1M entry cache limit with clear-on-full eviction to prevent memory exhaustion
  • Refactored evaluation loop to iterate over actual pieces (16-32) instead of all 64 squares

The changes are well-implemented and maintain evaluation correctness. The optimizations reduce computational overhead without altering search behavior. The cache eviction strategy is simple but effective for preventing unbounded memory growth during extended searches.

Confidence Score: 5/5

  • Safe to merge - performance optimizations with no behavioral changes
  • All optimizations are sound: Zobrist hashing is standard in chess engines, piece iteration reduces unnecessary work, and cache limiting prevents memory issues. No logical errors or breaking changes detected.
  • No files require special attention

Important Files Changed

Filename Overview
moonfish/psqt.py Optimized evaluation caching with Zobrist hashing and piece iteration; no logical issues found

Sequence Diagram

sequenceDiagram
    participant Engine as Alpha-Beta Engine
    participant Cache as board_evaluation_cache
    participant Eval as board_evaluation
    participant Board as chess.Board
    
    Engine->>Cache: call board_evaluation(board)
    Cache->>Board: zobrist_hash(board)
    Board-->>Cache: int hash key
    
    alt Cache hit
        Cache-->>Engine: return cached value
    else Cache miss
        alt Cache full (≥1M entries)
            Cache->>Cache: clear entire cache
        end
        Cache->>Eval: call board_evaluation(board)
        Eval->>Eval: get_phase(board)
        loop For each piece type
            Eval->>Board: pieces(piece_type, WHITE)
            Board-->>Eval: SquareSet of white pieces
            loop For each white piece
                Eval->>Eval: mg_white += table[square^56] + value
                Eval->>Eval: eg_white += table[square^56] + value
            end
            Eval->>Board: pieces(piece_type, BLACK)
            Board-->>Eval: SquareSet of black pieces
            loop For each black piece
                Eval->>Eval: mg_black += table[square] + value
                Eval->>Eval: eg_black += table[square] + value
            end
        end
        Eval->>Eval: calculate tapered eval based on phase
        Eval-->>Cache: evaluation score
        Cache->>Cache: store in cache[hash]
        Cache-->>Engine: return evaluation score
    end
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, no comments

Edit Code Review Agent Settings | Greptile

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🔬 Stockfish Benchmark Results

vs Stockfish Skill Level 3

Metric Wins Losses Draws Total Win %
Overall 27 60 13 100 27.0%
As White 19 23 8 50 38.0%
As Black 8 37 5 50 16.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 11

vs Stockfish Skill Level 4

Metric Wins Losses Draws Total Win %
Overall 17 77 6 100 17.0%
As White 11 35 4 50 22.0%
As Black 6 42 2 50 12.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 6

vs Stockfish Skill Level 5

Metric Wins Losses Draws Total Win %
Overall 8 89 3 100 8.0%
As White 3 45 2 50 6.0%
As Black 5 44 1 50 10.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 2
  • Draw by insufficient mating material: 1
Configuration
  • 5 chunks × 20 rounds × 3 skill levels = 300 total games
  • Each opening played with colors reversed (-repeat) for fairness
  • Moonfish: 60s per move
  • Stockfish: 60+5 time control

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

🔬 Stockfish Benchmark Results

vs Stockfish Skill Level 3

Metric Wins Losses Draws Total Win %
Overall 27 62 11 100 27.0%
As White 15 29 6 50 30.0%
As Black 12 33 5 50 24.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 7
  • Draw by insufficient mating material: 1
  • Draw by fifty moves rule: 1

vs Stockfish Skill Level 4

Metric Wins Losses Draws Total Win %
Overall 26 65 9 100 26.0%
As White 18 26 6 50 36.0%
As Black 8 39 3 50 16.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 9

vs Stockfish Skill Level 5

Metric Wins Losses Draws Total Win %
Overall 8 87 5 100 8.0%
As White 4 44 2 50 8.0%
As Black 4 43 3 50 8.0%

Non-checkmate endings:

  • Draw by 3-fold repetition: 4
Configuration
  • 5 chunks × 20 rounds × 3 skill levels = 300 total games
  • Each opening played with colors reversed (-repeat) for fairness
  • Moonfish: 60s per move
  • Stockfish: 60+5 time control

luccabb and others added 2 commits February 15, 2026 22:27
Performance optimizations for board evaluation:

- Use Zobrist hash instead of FEN string for cache keys (faster hashing)
- Add cache size limit (1M entries) to prevent unbounded memory growth
- Optimize board_evaluation() to iterate over pieces instead of all 64 squares
  - Typically 16-32 pieces vs always 64 squares
  - Reduces unnecessary iterations by ~50-75%

These changes improve evaluation speed without affecting correctness.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The cache adds complexity without significant benefit for typical
game sessions (10k-100k unique positions). Removes the dictionary,
decorator, and unused imports.
@luccabb luccabb force-pushed the feature/psqt-evaluation-performance branch from 42e6fc1 to 6c5f8af Compare February 16, 2026 06:34
@luccabb
Copy link
Owner Author

luccabb commented Feb 16, 2026

/run-nps-benchmark

@github-actions
Copy link

⚡ NPS Benchmark Results

Metric Value
Depth 5
Positions 48
Total nodes 20949384
Total time 2321.35s
Nodes/second 9024

Node count is the primary signal — it's deterministic and catches search behavior changes. If the node count changes, the PR changed search behavior. NPS is informational only (CI runner performance varies).

Per-position breakdown
Position  1/48: nodes=155074     time=12.73s  nps=12177
Position  2/48: nodes=606593     time=61.64s  nps=9840
Position  3/48: nodes=13225      time=0.89s  nps=14846
Position  4/48: nodes=750165     time=81.23s  nps=9235
Position  5/48: nodes=109167     time=13.25s  nps=8241
Position  6/48: nodes=500245     time=56.34s  nps=8879
Position  7/48: nodes=283405     time=31.49s  nps=8999
Position  8/48: nodes=344459     time=28.16s  nps=12230
Position  9/48: nodes=2113446    time=211.09s  nps=10011
Position 10/48: nodes=408907     time=33.82s  nps=12089
Position 11/48: nodes=516720     time=60.90s  nps=8485
Position 12/48: nodes=1134648    time=140.22s  nps=8091
Position 13/48: nodes=515044     time=56.92s  nps=9049
Position 14/48: nodes=838712     time=84.27s  nps=9952
Position 15/48: nodes=982014     time=101.99s  nps=9628
Position 16/48: nodes=315039     time=30.18s  nps=10439
Position 17/48: nodes=10112      time=0.67s  nps=15024
Position 18/48: nodes=15323      time=0.78s  nps=19666
Position 19/48: nodes=48718      time=3.87s  nps=12572
Position 20/48: nodes=89369      time=6.59s  nps=13561
Position 21/48: nodes=18259      time=1.15s  nps=15914
Position 22/48: nodes=662        time=0.03s  nps=19393
Position 23/48: nodes=11402      time=0.68s  nps=16715
Position 24/48: nodes=25010      time=1.74s  nps=14343
Position 25/48: nodes=8781       time=0.52s  nps=16972
Position 26/48: nodes=60646      time=5.13s  nps=11812
Position 27/48: nodes=74122      time=5.13s  nps=14458
Position 28/48: nodes=258609     time=23.33s  nps=11083
Position 29/48: nodes=301542     time=32.24s  nps=9353
Position 30/48: nodes=2451       time=0.19s  nps=13212
Position 31/48: nodes=1431889    time=148.15s  nps=9665
Position 32/48: nodes=854162     time=78.23s  nps=10918
Position 33/48: nodes=2479630    time=417.18s  nps=5943
Position 34/48: nodes=1469013    time=208.57s  nps=7043
Position 35/48: nodes=307495     time=25.95s  nps=11849
Position 36/48: nodes=1770804    time=171.99s  nps=10295
Position 37/48: nodes=1289243    time=114.63s  nps=11247
Position 38/48: nodes=14229      time=0.67s  nps=21387
Position 39/48: nodes=7885       time=0.29s  nps=27030
Position 40/48: nodes=23313      time=0.39s  nps=59040
Position 41/48: nodes=87269      time=6.34s  nps=13756
Position 42/48: nodes=81449      time=5.85s  nps=13911
Position 43/48: nodes=29486      time=2.05s  nps=14415
Position 44/48: nodes=125063     time=11.13s  nps=11240
Position 45/48: nodes=46664      time=3.71s  nps=12571
Position 46/48: nodes=419921     time=39.04s  nps=10755
Position 47/48: nodes=0          time=0.00s  nps=0  (terminal)
Position 48/48: nodes=0          time=0.00s  nps=0  (terminal)

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.

1 participant