Files
coorl-lost-cities/docs/research/fast-engine-next-optimizations.md
coolguyandClaude Opus 4.7 0f85fa85b3 Close librarian: full archive promote-survey + parallel dispatch
Second survey processed the remaining 12 archives via gemini after
the first batch of 3 was accepted. 12 drafts, 0 skips, 0 errors.
Every draft carries a deterministic Last-verified header
(2026-05-08, commit 5c221fb) thanks to the post-processing fix
landed in the previous commit. All 12 accepted into docs/research/
verbatim:

  deep-cfr-evaluation-profile-plan
  deep-cfr-legacy-experiment-reproduction
  deep-cfr-legacy-runtime-comparison
  deep-cfr-performance-experiments
  deep-cfr-profile-advantage-memory-split
  deep-cfr-profile
  deep-cfr-regret-fallback-audit
  deep-cfr-v0-gap-vs-coolrl
  deep-cfr-v0-plan
  fast-engine-next-optimizations
  post-a-optimization-calculus
  test-coverage-notes

docs/archive/ is now fully covered: every entry either has a
research counterpart by stem or by tail-match.

Also extracts _dispatch_one and adds --parallel N to
scripts/librarian_survey.py. ThreadPoolExecutor over the per-archive
work is safe because subprocess.run is network-bound (no GIL fight)
and each thread writes to its own output filename. Default stays
1 (sequential); --parallel 4 is the recommended speedup for large
surveys. The two surveys above ran sequentially; future runs can
opt in.

Plan declares librarian closed for new feature work. MEMORY drift
fixup and duplicate-merge modes stay deferred until a real input
surfaces. Stage 1 (5 deterministic checks) and Stage 2 (promote +
survey) remain operational.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 02:33:51 +09:00

4.2 KiB
Raw Permalink Blame History

Fast Engine Optimization Architecture

Last verified: 2026-05-08, commit 5c221fb

Source: docs/archive/fast-engine-next-optimizations.md

Question

How should the Cython-based game engine be optimized to support the high-throughput requirements of reinforcement learning algorithms like Deep CFR?

Analysis

The core of the Lost Cities implementation (the "fast engine") is built as a Cython extension to provide a high-performance alternative to pure-Python game logic. While the current architecture provides the necessary speed for basic experimentation, it contains several intentional design choices—such as cpdef method wrappers and fragmented memory management—that become significant bottlenecks during large-scale self-play and traversal.

Cython pxd API vs. Python Boundaries

The engine currently exposes its logic primarily through cpdef methods in src/coolrl_lost_cities/games/classic/game.pyx. While these methods are accessible from both Python and Cython, calling them from the Python interpreter incurs conversion overhead for every argument and return value. For algorithms like Deep CFR that visit millions of nodes per iteration, this overhead is prohibitive.

To achieve maximum performance, traversal code must bypass the Python interpreter entirely. This is possible by importing the game state's C-level API directly from src/coolrl_lost_cities/games/classic/game.pxd. High-performance modules, such as the Deep CFR trainer, should favor cdef methods like _apply_action_unchecked_c and _legal_actions_c which operate on raw C types and pointers.

Contiguous Memory Allocation

In its current state, the GameState class manages its internal arrays using multiple discrete memory allocations. In game.pyx at lines 314323, the _configure method performs approximately eleven separate malloc calls to set up the deck, hands, expeditions, and scoring buffers:

self.deck_cards = <int*>malloc(self.total_cards * sizeof(int))
self.hand_cards = <int*>malloc(2 * self.hand_size * sizeof(int))
# ... and several others

This fragmented allocation pattern increases memory management overhead and can lead to poor cache locality during state transitions. Consolidating these buffers into a single contiguous block of memory would not only improve cache performance but also simplify state cloning. A single memcpy could duplicate the entire game state, significantly speeding up the recursive branching required by CFR-based traversers.

Zero-Copy Feature Extraction

A major cost in the current RL pipeline is the construction of observation vectors and action masks. Methods like unified_legal_mask_np (line 797 in game.pyx) currently build Python lists of booleans before converting them into NumPy arrays. This "build-and-convert" pattern generates excessive Python object churn in the inner loop of training.

The engine must transition toward a "zero-copy" paradigm where observation and mask construction logic writes directly into pre-allocated destination buffers. This pattern is already partially implemented in src/coolrl_lost_cities/games/classic/deep_cfr/encoding.pyx with the encode_info_state_c function, which accepts a float* pointer. Expanding this approach to all high-frequency data paths—including legal action masks—is essential for eliminating Python-level overhead during training.

Practical Implication

Performance-critical components—specifically interleaved traversal and batched inference servers—should be designed to interface with the engine at the C level. Future refactoring of the GameState should prioritize a single-allocation memory model to enable rapid cloning and cache-efficient updates. All feature extraction logic must move toward buffer-writing APIs to ensure that node traversal speed is limited by algorithmic complexity rather than Python infrastructure.

References

  • src/coolrl_lost_cities/games/classic/game.pxd: Direct C-API definitions for the fast engine.
  • src/coolrl_lost_cities/games/classic/game.pyx: Core state management and malloc-based allocation logic.
  • src/coolrl_lost_cities/games/classic/deep_cfr/encoding.pyx: Reference implementation for buffer-based feature extraction.