perf: cache the matching archetypes per query#33
Merged
Conversation
Every Foreach/Task/Count/GetEntitiesIds rebuilt the query's filter id list and rescanned all archetypes (O(archetypes)), allocating and copying the matched archetype structs on every call. Each query now holds a shared filterCache: the filter ids (required components + tags) are computed once at creation, and the matching archetype ids are memoized in a reused buffer, recomputed only when a new archetype appears — detected via len(world.archetypes) as a monotonic version, since archetypes are never removed. Query methods iterate the cached ids and read world.archetypes[id] live, so entities moving between existing archetypes are still seen. A reused query over a world with many archetypes now costs the same regardless of archetype count (O(archetypes) -> O(1)) and allocates nothing on the read path. getArchetypesForComponentsIds is removed (replaced by World.matchArchetypes). Measured on go-ecs-benchmarks (Ryzen 5800X), query reused across calls: - query256arch: -88% (N=1), -97% (N=64), -90% (N=256) vs v1.7.0 - query32arch: -87% to -90% at low/mid N - random (mixed load): -40% across N
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every Foreach/Task/Count/GetEntitiesIds rebuilt the query's filter id list and rescanned all archetypes (O(archetypes)), allocating and copying the matched archetype structs on every call.
Each query now holds a shared filterCache: the filter ids (required components + tags) are computed once at creation, and the matching archetype ids are memoized in a reused buffer, recomputed only when a new archetype appears — detected via len(world.archetypes) as a monotonic version, since archetypes are never removed. Query methods iterate the cached ids and read world.archetypes[id] live, so entities moving between existing archetypes are still seen.
A reused query over a world with many archetypes now costs the same regardless of archetype count (O(archetypes) -> O(1)) and allocates nothing on the read path. getArchetypesForComponentsIds is removed (replaced by World.matchArchetypes).
Measured on go-ecs-benchmarks (Ryzen 5800X), query reused across calls: