Skip to content

Fix/performance optimisations#1455

Merged
hillerstorm merged 30 commits into
masterfrom
fix/performance-optimisations
Jul 22, 2026
Merged

Fix/performance optimisations#1455
hillerstorm merged 30 commits into
masterfrom
fix/performance-optimisations

Conversation

@1337LutZ

@1337LutZ 1337LutZ commented Jun 4, 2026

Copy link
Copy Markdown

Performance optimisations — sim core hot path

Targeted profiling pass across six benchmark specs (Frost DK, Unholy DK, Windwalker, Affliction, Demonology, Beast Mastery). No behaviour changes except one rollover bug fix noted below and one aura-expiry fix from review (see Review follow-ups).


Changes

sim/core/apl_action.go — IsReady / condition evaluation order

impl.IsReady(sim) is now evaluated before condition.GetBool(sim). Spell-unavailability checks (cooldown, insufficient resources) are significantly cheaper than evaluating a condition expression tree, so 50–70% of priority-list entries short-circuit before any condition logic runs.

sim/core/apl_values_dot.go — DoT APL value result caching

APLValueDotPercentIncrease, APLValueDotCritPercentIncrease, and APLValueDotTickRatePercentIncrease now cache their float64 result per evalGeneration. These call ExpectedTickDamage() (float math + snapshot lookup) on every evaluation; caching is high-value for Affliction and Unholy where the same dot-value node is checked multiple times per APL scan.

sim/core/apl_values_operators.goAPLValueCompare lhsType caching

APLValueCompare.lhsType is now cached at construction time, eliminating a virtual dispatch call on every comparison evaluation.

sim/core/spell.go + sim/core/target.go + sim/core/unit.goexpectedDamageCache slice migration

Three per-AttackTable maps (map[*Spell]*ExpectedDamageCalculatorCache) replaced with per-spell slices indexed by target.UnitIndex. Eliminates pointer hashing on every DoT tick damage calculation and removes lazy allocation per new target. Caches now cleared in spell.reset() (per-iteration) rather than scattered across unit.reset().

sim/core/runic_power.go — single reused rune PA

The rune regen PendingAction is allocated once per DK lifetime and reused on every reschedule. Previously a new &PendingAction{...} plus closure was heap-allocated on each rune regen event. Also fixes make([]int8, 0)lastRegen[:0] to avoid a slice allocation per regen tick.

sim/core/aura.go — two-pass aura expiry

auraTracker.advance() now collects all expired auras into a reusable scratch slice in a single scan while simultaneously computing minExpires for survivors. A second pass re-checks expiry and calls Deactivate on each. The outer loop handles the rare case where a Deactivate callback expires additional auras. Common case (no expirations at this timestamp) pays exactly one scan and returns immediately.

sim/core/aura.go — O(1) GetAura label lookup

Added aurasByLabel map[string]*Aura to auraTracker, populated in registerAura(), mirroring the existing aurasByTag pattern. GetAura now returns at.aurasByLabel[label] directly instead of scanning the full at.auras slice (~200 entries per unit).

sim/monk/ww_storm_earth_and_fire.go — SEF zero-alloc clone lists

updateActiveClones() previously called make([]*StormEarthAndFirePet, 0, 3) twice on every state change. Both slices are now pre-allocated in the constructor and reset with [:0] on each update. Also removed an early break in CastCopySpell on nil spell lookup — changed to fall-through to avoid silently skipping remaining pets if spellbooks diverge in future.


Review follow-ups (7c60e6c)

All review comments addressed:

  • Aura expiry re-check restored (behaviour fix). The batched advance() loop had dropped the per-aura expiry re-check that the old goto restart provided, so an aura refreshed by an earlier same-tick OnExpire (e.g. Arcane Missiles expiry activating Arcane Charges) was wrongly faded. Restoring the guard moved six spec suites (Arcane, Frost Mage, Balance, Guardian, Brewmaster, Prot Paladin) back to master's exact expected results — the branch's earlier .results updates had encoded the bug. Updated results files accordingly.
  • replacePlaceholders compare/math rebuilds now set the cached lhsType/rhsType (the group-reference rebuild previously produced compares that always evaluated false). Added a regression test for the placeholder→compare path.
  • APLValueMath now caches both operand types at construction, finishing the Type()/dispatch optimisation that APLValueCompare started.
  • aurasByLabel keyed by string instead of maphash uint64 — Go AES-hashes string keys natively, so O(1) is kept without the (astronomically unlikely but silent) collision risk.
  • SEF CastCopySpell iterates the stable pets slice, since deactivateClone compacts activeClones in place under an in-flight range.
  • advance() scratch buffer is a reusable field on auraTracker instead of a fixed [16] stack array — no zeroing cost, no overflow protocol.
  • launchPA guard is now gated on !rp.pa.cancelled, replacing the NextActionAt = NeverExpires sentinel in runicPowerBar.reset().
  • Dot-increase APL values share a cached() helper (verified to fully inline, zero closure allocations) and the evalGeneration bump contract is documented on the field.

Re-benchmarked after the fixes (Ryzen 9 9950X3D, -benchtime=3s -count=6, benchstat, all p=0.002):

Spec vs master
Frost DK (masterfrost) −16.5%
Unholy DK −13.7%
Windwalker Monk −5.9%
Affliction Warlock −7.4%
Demonology Warlock −4.4%

Benchmark results

Two measurements, both vs unmodified master on an i5-13600K.

Micro-benchmarks (go test -bench -benchtime=10s, empty equipment, 1 sim iteration × 300 s fight per op):

Spec Master (ns/op) Branch (ns/op) Δ
Frost DK (masterfrost) 2,209,390 1,984,413 −10.2%
Unholy DK 3,602,756 3,256,357 −9.6%
Windwalker Monk 2,160,024 2,044,743 −5.3%
Affliction Warlock 1,725,758 1,600,646 −7.2%
Demonology Warlock 3,083,250 2,795,854 −9.3%

@github-actions github-actions Bot removed the Priest label Jul 5, 2026
@github-actions github-actions Bot removed the Hunter label Jul 13, 2026

@hillerstorm hillerstorm left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Adversarial review of the engine changes (results churn excluded). Two correctness bugs, one latent regression, and a handful of robustness/cleanup suggestions — details inline. The two bugs worth fixing before merge are the group-APL lhsType issue and the SEF clone-list mutation.

Comment thread sim/core/apl_values_operators.go
Comment thread sim/monk/ww_storm_earth_and_fire.go
Comment thread sim/core/aura.go Outdated
Comment thread sim/core/aura.go Outdated
Comment thread sim/core/apl_values_dot.go Outdated
Comment thread sim/core/runic_power.go Outdated
Comment thread sim/core/aura.go Outdated
Comment thread sim/core/apl_values_operators.go
- Set lhsType/rhsType when rebuilding APLValueCompare/APLValueMath in
  replacePlaceholders, and add a regression test for the placeholder
  compare path
- Cache operand types in APLValueMath to finish the Type() optimisation
- Restore the aura expiry re-check lost in the batched advance() loop so
  same-tick OnExpire refresh chains (e.g. Arcane Missiles -> Arcane
  Charges) are not faded; several spec results move back to master values
- Replace the fixed [16] stack buffer in advance() with a reusable
  scratch slice on auraTracker
- Key aurasByLabel by string instead of maphash to rule out collisions
- Iterate the stable pets slice in SEF CastCopySpell since
  deactivateClone compacts activeClones in place
- Gate the launchPA guard on pa.cancelled instead of the NeverExpires
  sentinel in runicPowerBar.reset
- Extract a shared evalGeneration cache helper for the dot increase APL
  values and document the evalGeneration bump contract
@hillerstorm
hillerstorm merged commit d54583f into master Jul 22, 2026
2 checks passed
@hillerstorm
hillerstorm deleted the fix/performance-optimisations branch July 22, 2026 17:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants