Skip to content

Reuse EngineQuerier to reduce allocation pressure#1843

Open
DavidLegg wants to merge 1 commit into
perf/clipper-apgen-translationfrom
perf/engine-querier-reuse
Open

Reuse EngineQuerier to reduce allocation pressure#1843
DavidLegg wants to merge 1 commit into
perf/clipper-apgen-translationfrom
perf/engine-querier-reuse

Conversation

@DavidLegg

Copy link
Copy Markdown
Contributor

No description provided.

@DavidLegg
DavidLegg requested a review from a team as a code owner July 6, 2026 18:24
@DavidLegg
DavidLegg requested review from JoelCourtney and joswig July 6, 2026 18:25
Comment on lines +1172 to 1178
void reset(final TaskFrame<JobId> frame) {
this.frame = Objects.requireNonNull(frame);
// subscribeQuery takes ownership of the previous set, so allocate a new one
this.referencedTopics = new HashSet<>();
this.expiry = Optional.empty();
this.stateCache.clear();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This does almost as much work as building a new engine querier, with the added complexity that now, because this is stateful, there's a chance we forget to reset the querier when needed. I'd like to see profiling data showing that this change makes a noticeable improvement before accepting this.

@Twisol Twisol Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(I'll also note that, like on the other PR, a querier shouldn't be a very long-lived object, since it only exists for the duration of a condition or resource invocation. The point of a generational GC is to deal with short-lived objects more efficiently, so without profiling I'd be skeptical that, out of all of the transient objects created over the course of a simulation, the querier is a particular hotspot.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After talking to Brad a little more, it seems that Claude thinks the main benefit here is in keeping stateCache around. That initial size of 32, according to Claude, puts non-negligible pressure on the GC to allocate and deallocate it so often.

In my own profiling, I've seen updateCondition come up as a hot path before, so the idea that allocations in that function might drive performance isn't totally crazy to me. I'll work with Brad to see if we can get more detailed data on this.

@Twisol Twisol Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting. It looks like the stateCache is new to perf/clipper-apgen-translation (via 12db906), correct? Do we know whether that improved performance at the time? How was the initial size chosen -- does the cache tend to get filled or does it often need to get resized even larger? The idea of a query cache is a good one in principle, but it seems like we're optimizing an optimization, so it might be worth revisiting the original decision holistically.

The other thing I'd say is, by design, this cache probably saves a lot of unnecessary state copies (and, unlike in the other PR, resources and conditions should never be maintaining local mutable state, so there's little to no danger to resources sharing the same cached copy). We're basically saving an allocation for every cache hit, so the cache's own allocation should matter less exactly if the cache is a good optimization.

On the other hand, constantly flushing and recreating the cache during the same simulation instant works directionally against the optimization niche the cache targets. In principle, this cache could be lifted all the way to the engine toplevel, and maintained over the course of a whole SimulationInstant (covering both conditions and resources). The cache could then be passed to the constructor of EngineQuerier instead of maintaining and resetting a whole mutable EngineQuerier.

Might be worth investigating. There's a lot of hypotheticals floating around this however, so (and I think we agree here) profiling data is really essential for good decision-making. At minimum, I think pulling the cache out to the engine toplevel and passing it to EngineQuerier on construction would be a reasonable way to scope the GC and maintenance impact of the cache. The choice of cache invalidation policy would be orthogonal.

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.

3 participants