mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-08 03:29:44 +00:00
perf: cut core-sim GC churn another 36% (75% cumulative) (#4498)
## Summary Round 3 of GC-churn reduction (follow-up to #4494 and #4496). All changes are **behavior-preserving** — final game-state hash unchanged on three seeded runs. ### Changes | Site | Change | Churn target | |---|---|---| | `MiniMapTransformer` | Path upscaling works in pure numeric coordinates and emits main-map `TileRef`s directly, replacing three intermediate `Cell`-object arrays per path (cell path → scaled path → smoothed path → final map). Identical arithmetic, so identical rounding and identical tiles. | ~7.1 GB | | `ShoreCoercingTransformer` | Reused neighbor buffers instead of `neighbors()` arrays; no per-call `{water, original}` objects. Tie-breaking preserved (helpers share the unified N,S,W,E order since #4495). | ~1.5 GB | | `diffPlayerUpdate` | Allocation-free all-equal fast path. Runs per player per tick and usually returns `null` (gold/troops/tiles travel via packed arrays), but previously allocated the diff object + a closure first. Field list matches the diff exactly. | ~2.6 GB | | Large-`Set` iteration | `for..of` over a `Set` allocates an iterator-result object per element — significant on 100k-tile border sets. `calculateClusters`, `calculateBoundingBox` (indexed fast path for arrays too) and `getAttackFrontTiles` (also dropped its `neighbors()` arrays) now use `Set.forEach`. | ~3.6 GB | ### Results (Giant World Map, 400 bots, 12,000 ticks, seed `perf-default`) | Metric | Before | After | vs. original (pre-#4494) | |---|---|---|---| | Sampled allocations (incl. collected) | 37.8 GB | **24.1 GB (−36%)** | 97.7 GB (**−75%**) | | Ticks/sec | 82 | **88** | 66 (+33%) | | Mean / p99 tick | 12.2 / 36.0 ms | 11.3 / 34.8 ms | 15.2 / 49.9 ms | | Peak heap | 762 MB | 529 MB | 758 MB | ## Determinism Final hash unchanged on all three reference runs: - Giant World Map 12,000 ticks: `57830793797434300` ✓ - Giant World Map 2,000 ticks: `55125379638382860` ✓ - World 1,800 ticks: `32337437717390864` ✓ ## Test plan - [x] Full suite green (1,906 tests; the `getAttackFrontTiles` test stub gained a `neighbors4` implementation to match the real interface) - [x] Hash equality on 3 seeded headless runs (2 maps) - [x] Before/after 20-min GC benchmarks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,43 @@ export function diffPlayerUpdate(
|
||||
prev: PlayerUpdate,
|
||||
next: PlayerUpdate,
|
||||
): PlayerUpdate | null {
|
||||
// Fast path: this runs for every player every tick and usually finds no
|
||||
// changes (tilesOwned/gold/troops travel separately) — return without
|
||||
// allocating anything. The comparisons repeat below only for the rare
|
||||
// changed player.
|
||||
if (
|
||||
prev.clientID === next.clientID &&
|
||||
prev.name === next.name &&
|
||||
prev.displayName === next.displayName &&
|
||||
prev.team === next.team &&
|
||||
prev.smallID === next.smallID &&
|
||||
prev.playerType === next.playerType &&
|
||||
prev.isAlive === next.isAlive &&
|
||||
prev.isDisconnected === next.isDisconnected &&
|
||||
prev.isTraitor === next.isTraitor &&
|
||||
prev.traitorRemainingTicks === next.traitorRemainingTicks &&
|
||||
prev.inDoomsdayClock === next.inDoomsdayClock &&
|
||||
prev.markedDoomsdayClockTick === next.markedDoomsdayClockTick &&
|
||||
prev.hasSpawned === next.hasSpawned &&
|
||||
prev.spawnTile === next.spawnTile &&
|
||||
prev.betrayals === next.betrayals &&
|
||||
prev.lastDeleteUnitTick === next.lastDeleteUnitTick &&
|
||||
prev.isLobbyCreator === next.isLobbyCreator &&
|
||||
numberArrayEqual(prev.allies, next.allies) &&
|
||||
numberArrayEqual(prev.targets, next.targets) &&
|
||||
stringArrayEqual(
|
||||
prev.outgoingAllianceRequests,
|
||||
next.outgoingAllianceRequests,
|
||||
) &&
|
||||
stringSetEqual(prev.embargoes, next.embargoes) &&
|
||||
emojiArrayEqual(prev.outgoingEmojis, next.outgoingEmojis) &&
|
||||
attackArrayMembershipEqual(prev.outgoingAttacks, next.outgoingAttacks) &&
|
||||
attackArrayMembershipEqual(prev.incomingAttacks, next.incomingAttacks) &&
|
||||
allianceArrayEqual(prev.alliances, next.alliances)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const diff: PlayerUpdate = { type: GameUpdateType.Player, id: next.id };
|
||||
let changed = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user