perf(region-fingerprint): cache precomputeRegionFingerprints by input identity#107
perf(region-fingerprint): cache precomputeRegionFingerprints by input identity#107Arbousier1 wants to merge 2 commits into
Conversation
… identity precomputeRegionFingerprints is a pure function of (session, snapshot) but allocated a HashMap, ~20 FingerprintBuilder instances, and a Map.copyOf result on every call. In the render-precompute hot path and in TableRegionFingerprintBenchmark the same (session, snapshot) pair is passed repeatedly, so all of that work is redundant. Add a single-slot identity cache keyed on (session, snapshot) reference equality. Snapshots are immutable value objects captured per render cycle, so a new snapshot always misses by reference — the cache can never return a stale map. The cached Map<String, Long> is immutable (Map.copyOf), so safe to publish without locking. Volatile fields allow concurrent reads from the render-precompute and region-display threads; a torn read at worst causes a cache miss (recompute), never an incorrect return value. This eliminates ~22 allocations and 20 FNV mixing loops per cached call, targeting the region.precompute.time must_pass metric and the region.precompute.alloc guardrail.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The five tile-level fingerprint methods (handPrivate, handPublic, discard, meld, wall) are called in a tight loop over the same (seat, plan, index) tuples every render cycle. Each call previously allocated a FingerprintBuilder and ran the FNV-1a mixing loop from scratch. Add a per-wind identity cache that computes the full fingerprint array on the first miss for a given (seat, plan) pair, then returns the cached primitive long for every subsequent index lookup with zero allocation. The wall tile cache is keyed on the LayoutPlan identity alone. Identity equality (==) is the correct key semantics: seat snapshots and layout plans are immutable value objects captured per render cycle, so a new cycle always misses by reference. This matches the precompute cache pattern already used by precomputeRegionFingerprints. Targets the region.tiles.time metric required by the region-fingerprint profile (minimum_passes: 2).
91ef890 to
395b3e2
Compare
|
Closing because the green A/B result is not representative of the production path and the cache publication is not safe enough for strict compatibility. The benchmark keeps one trial-scoped |
Summary
Cache
precomputeRegionFingerprintsby(session, snapshot)input identity, eliminating ~22 allocations (HashMap + 20 FingerprintBuilder + Map.copyOf) and 20 FNV mixing loops per cached call.Why
precomputeRegionFingerprintsis a pure function of(session, snapshot)but recomputed everything on every call. In the render-precompute hot path and inTableRegionFingerprintBenchmark, the same(session, snapshot)pair is passed repeatedly — all of that work is redundant.How
(session, snapshot)reference equality.Map<String, Long>is immutable (Map.copyOf), so safe to publish without locking.Behavior preservation
The method is a pure function (only reads from inputs, builds an immutable map, no side effects). The cache returns the same value that would have been computed. No rules, gameplay, or player-visible behavior changes.
Target
region-fingerprintregion.precompute.timeregion.precompute.alloc