Skip a name lookup that cannot change the result - #11975
MostCromulent wants to merge 2 commits into
Conversation
getName walks a table of name-changing effects on every call, and that table is empty for almost every card in almost every game. The table is a sorted structure, so asking for its contents builds a view and starts an iteration before finding nothing, while asking whether it is empty is a single check. The damage replacement code makes this expensive. Card.staticReplaceDamage and Player.staticReplaceDamage each walk the whole battlefield and compare a card's name against about fifteen hardcoded names in turn, so a card matching none of them pays the empty walk fifteen times per damage event. AI-vs-AI games on token decks run 7.8 percent faster. Ordinary constructed and four-player commander play byte-identical games and show no change outside measurement noise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
B seems safer with clearer architecture imo |
A Card declares twenty-three TreeBasedTable fields for the continuous effects that can change its name, types, colours, keywords, traits, mana cost, SVars and power and toughness. All twenty-three are built when the card is, and for most cards most of them stay empty for the whole game. Six AI-vs-AI games on token decks construct about 45,000 cards, so that is roughly a million empty tables built and discarded. The fields now start as a small wrapper around a shared immutable empty table and swap in a real TreeBasedTable the first time something is put into one. Reads, removes and clears on an untouched table answer from the empty one without building anything. No call site changes, and the fields stay final. This replaces the getName guard from the previous commit, which skipped one of these empty walks at one accessor. Starting the table empty removes the walk everywhere instead. AI-vs-AI games, four repetitions per arm, arms alternated and the order reversed between reps: token decks 44,604 ms to 42,229 ms, 5.3 percent faster. Ordinary constructed and four-player commander replay byte-identically on both arms, every game giving the same log hash across all four repetitions, so their differences are the same work timed twice and both sit inside the run-to-run variance of those workloads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
i'm unsure about this new LazyTable the whole TreeTable probably needs to be changed later anyway when we finally update all the effects into being static abilities for Dependency |
Summary
Card.getNamewalks a table of name-changing effects on every call, and that table is empty for almost every card in almost every game. Returning the printed name directly when it is empty leaves every result unchanged and makes AI-vs-AI games on token decks faster.Where this fits
Token decks spend 83.5% of their time in AI combat prediction, and the recent merges (#11946, #11951, #11956, #11961, #11963, #11967) removed much of the engine work underneath it.
Card.getNameis now 7.4% of profiler samples on mirrored Goblin token decks, 98% of it reached fromCard.staticReplaceDamageandPlayer.staticReplaceDamage. The same guard measured −1.1% with overlapping ranges before those merges; the work that was hiding it is gone.Both methods walk the battlefield and compare
c.getName()against roughly fifteen hardcoded names, so a card matching none of them pays the empty walk fifteen times per damage event.changedCardNamesis aTreeBasedTable:values()builds a view and begins iterating before finding nothing, whileisEmpty()is one check on the backing map.Implementation
Four lines in
Card.getName(CardState): returnstate.getName()whenchangedCardNamesis empty. The existing loop never assigned anything in that case, so the returned value is identical, and the added check is constant time.Measurements
AI-vs-AI games, four repetitions per arm, arms alternated and the order reversed between reps.
On token decks the slowest run of the new code beat the fastest run of the old. Commander and ordinary constructed replay byte-identically on both arms, every game giving the same log hash across all four repetitions, so their differences are the same work timed twice and both sit well inside the run-to-run variance of those workloads.
🤖 Generated with Claude Code