Skip to content

Performance optimisations for simulation calculate and parameter lookups#436

Open
nikhilwoodruff wants to merge 6 commits intomasterfrom
perf/fast-cache
Open

Performance optimisations for simulation calculate and parameter lookups#436
nikhilwoodruff wants to merge 6 commits intomasterfrom
perf/fast-cache

Conversation

@nikhilwoodruff
Copy link
Contributor

@nikhilwoodruff nikhilwoodruff commented Feb 18, 2026

Two sets of performance improvements for the simulation hot path.

1. Fast cache for repeated variable lookups — Flat dict[(variable_name, str(period)), array] at the Simulation level, checked at the very top of calculate() before the tracer and full _calculate() machinery. Only active when map_to=None and decode_enums=False (the inner-loop hot path used by formulas calling dependencies). Invalidation mirrors the existing holder cache.

2. Vectorial parameter lookup optimisation — Replaces O(N×K) numpy.select in VectorialParameterNodeAtInstant.__getitem__ with O(N) index-based selection. For enum/EnumArray keys, builds a lookup table mapping integer codes directly to child indices, skipping string conversion. For string keys, uses numpy.unique to reduce comparisons. Also caches build_from_node results on ParameterNodeAtInstant to avoid rebuilding the recarray on every access.

US household_net_income compute: 12.8s → 9.0s (-30%). All existing tests pass (tracer test failures are pre-existing).

nikhilwoodruff and others added 5 commits December 10, 2025 19:24
Adds a flat dict[tuple[str,str], array] at the Simulation level, checked at the
top of calculate() before tracer, random seed and _calculate() machinery. Only
active when map_to=None and decode_enums=False (the inner-loop hot path).

Invalidation mirrors the existing holder cache:
- purge_cache_of_invalid_values() removes invalidated entries
- delete_arrays() removes the relevant key(s)
- clone() gets a fresh empty cache to prevent cross-simulation sharing

Uses getattr/hasattr guards so StubSimulation and other test subclasses that
bypass __init__ work without modification.

Co-Authored-By: Claude <noreply@anthropic.com>
Skip the fast path when tracing is enabled, so FullTracer records all
calculations correctly.

Co-Authored-By: Claude <noreply@anthropic.com>
@nikhilwoodruff
Copy link
Contributor Author

Good catch — the fast path was skipping tracer.record_calculation_start() even when trace=True. Added and not self.trace to the guard so FullTracer sees all calculations as before. No perf impact for the normal (non-trace) path.

Replace O(N×K) numpy.select with O(N) index-based selection in
VectorialParameterNodeAtInstant.__getitem__. For enum/EnumArray keys,
build a lookup table mapping integer codes directly to child indices,
avoiding the intermediate string conversion entirely. For string keys,
use numpy.unique to reduce N×K string comparisons to U dict lookups
(where U = unique keys, typically ≪ N).

Also cache build_from_node results on ParameterNodeAtInstant to avoid
rebuilding the recarray on every vectorial access.

US household_net_income compute: 12.8s → 9.0s (-30%).

Co-Authored-By: Claude <noreply@anthropic.com>
@nikhilwoodruff nikhilwoodruff changed the title Add _fast_cache to Simulation for O(1) repeated variable lookups Performance optimisations for simulation calculate and parameter lookups Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant