Skip to content

Speedup resource lookup in Res and ResMut#23174

Open
Trashtalk217 wants to merge 4 commits intobevyengine:mainfrom
Trashtalk217:speedup-resource-lookup
Open

Speedup resource lookup in Res and ResMut#23174
Trashtalk217 wants to merge 4 commits intobevyengine:mainfrom
Trashtalk217:speedup-resource-lookup

Conversation

@Trashtalk217
Copy link
Copy Markdown
Contributor

Objective

See #23039. Resources as components introduced an extra entity lookup for each resource access.

Solution

Store the resource entity in the system param state of Res and ResMut. This is possible because resource entities are stable, so the state doesn't have to be updated.

Benchmarking

I quickly ran

cargo run --release --example bevymark -- --waves 60 --per-wave 500 --benchmark --mode mesh2d

and got a frametime of 33.4 ms (on main 34.6 ms). This is a minor reduction, but it might help. Others should double-check that this isn't a fluke, because my benchmarking isn't the most airtight.

Testing

All tests pass.

@alice-i-cecile alice-i-cecile added this to the 0.19 milestone Feb 28, 2026
@alice-i-cecile alice-i-cecile requested a review from hymm February 28, 2026 23:28
@github-actions
Copy link
Copy Markdown
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-23174

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@github-actions
Copy link
Copy Markdown
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-23174

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-23174

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@Trashtalk217 Trashtalk217 added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 28, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in ECS Feb 28, 2026
#[inline]
unsafe fn validate_param(
&mut component_id: &mut Self::State,
&mut (component_id, entity): &mut Self::State,
Copy link
Copy Markdown
Member

@cart cart Feb 28, 2026

Choose a reason for hiding this comment

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

The redundant work in validate_param, which is done immediately before get_param never felt great to me. This isn't a problem exclusive to Res/ResMut, but it is certainly present.

I think doing validation as part of get_param is more defensible from a performance perspective.

I think it is worth doing some benchmarks where we skip validation (as-in, across all SystemParams), just to see what price we're paying here.

@SkiFire13
Copy link
Copy Markdown
Contributor

This is possible because resource entities are stable, so the state doesn't have to be updated.

This is not true:

#[test]
fn resource_component_stable() {
    #[derive(Resource)]
    struct Foo;

    let mut world = World::new();

    let component_id = world.register_component::<Foo>();

    world.insert_resource(Foo);

    let res_id1 = world.resource_entities().get(component_id).copied();

    world.remove_resource_by_id(component_id);
    world.insert_resource(Foo);

    let res_id2 = world.resource_entities().get(component_id).copied();

    assert_eq!(res_id1, res_id2); // Currently fails
}

If resource entities are meant to be stable then:

  • we should have tests that check this is the case
  • there should be documentation/comments in all places that might break this invariant to ensure future changes don't mistakingly break it
  • I would also argue that ResourceEntities should not expose a remove method

@hymm
Copy link
Copy Markdown
Contributor

hymm commented Mar 2, 2026

This is a decent perf improvement. I get about 0.4ms back of the 1.4ms I lost.

image

Though we probably need to figure out how to actually make the entities stable without any loopholes before we merge this.

@alice-i-cecile
Copy link
Copy Markdown
Member

Though we probably need to figure out how to actually make the entities stable without any loopholes before we merge this.

Or alternatively, have a way to invalidate all of these caches that does not regress perf.

@alice-i-cecile alice-i-cecile added D-Complex Quite challenging from either a design or technical perspective. Ask for help! S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 2, 2026
@alice-i-cecile alice-i-cecile removed this from the 0.19 milestone Mar 3, 2026
@alice-i-cecile alice-i-cecile changed the title Speedup resource lookup in Res en ResMut Speedup resource lookup in Res and ResMut Mar 3, 2026
github-merge-queue Bot pushed a commit that referenced this pull request Mar 11, 2026
…3225)

# Objective

As raised in
[#23174](#23174 (comment)),
we currently duplicate working when looking up our system parameters:
once during validation, and then again when actually fetching the data.

This is (maybe) slow, and would worsen the performance regression
incurred by resources-as-components (#19731).

This strategy also imposes some non-trivial complexity and
maintainability costs. Because "validate" is a distinct step from "use",
it's possible to skip validation! As far as I could tell, this is the
case in a number of places before this PR: particularly in the
unconventional "please just run my system" path. While in most cases
this will simply result in a crash in a different place, it causes these
paths to not handle

Fixes #23179. Fixes #15505.

## Solution

Fundamentally, what we're doing is rolling the
`SystemParam::validate_param` behavior into `SystemParam::get_param`, by
making the latter return a `Result`.

However, there is a tremendous amount of splash damage required to get
that to actually compile and expose the correct semantics. The most
important of these are:

- `SystemState::get` and friends now returns a `Result`
- this leads to a fair bit of assorted unwrap spam in our tests and
weird internal usages
- these tests can probably be refactored to not use `SystemState`
directly in the future now that we have better tools like
`run_system_once`, but eh, not this PR's job
- this is semantically correct, as these params could fail validation
- `System::validate_param_unsafe` has been removed, and validation now
occurs inside of `System::run_unsafe`
- very much a net positive for both abstract robustness and current
correctness
- this impacts the strategy that various executors use: see the next
section

There are a *lot* of moving parts here: I'm sorry that I couldn't get
this into a smaller, more incremental refactor. When reviewing this PR,
you should begin with the migration guide to help get you oriented on
the details: `validation_merging.md`.

From there, the most important files to review are:

1. `system_param.rs`: trait changes and implementers
2. `function_system.rs`: primary implementer of `System`
3. `multithreaded.rs`: the parallel executor

**NOTE TO REVIEWERS:** Please make comments to generate threads; this PR
review might get fairly hairy.

### Performance discussion

For the parallel `MultithreadedExecutor`, validation was previously done
as a cheap pre-validation step,
while checking run conditions.
Now, tasks will be spawned for systems which would fail or are skipped
during validation.

In most cases, avoiding the extra overhead of looking up the required
data twice should dominate.
However, this change may negatively affect systems which are frequently
skipped (e.g. due to `Single`).

### Paths not taken

In this PR, I've decided not to:

- Add another variant
[RunSystemError](https://docs.rs/bevy/latest/bevy/ecs/system/enum.RunSystemError.html),
distinguishing "validation failed" from "system ran but returned an
error".
- While reusing
[RunSystemError::Failed](https://docs.rs/bevy/latest/bevy/ecs/system/enum.RunSystemError.html#variant.Failed)
for both cases is messy, this PR is already a bit of a nightmare to
review.
- Return a result from `ParamSet::get_mut`.
   - Instead, we just `unwrap`.
- Bubbling up the `Result` is technically more correct, but these were
already panicking before if e.g. a resource is missing, and `ParamSet`
is already an ergonomic abomination.

## Testing

I've added a number of new tests to exercise the system param validation
paths, ensuring that validation is done when systems are run.

However, I would appreciate some help benchmarking the net impact on
realistic-ish scenes. `breakout`, `bevy_city` and `many_animated_foxes`
are probably a decent scattering, but I'd be very open to other
suggestions.

Having done this refactor, I think that it's a net improvement for
robustness and clarity even without the perf benefits however, and that
we should proceed unless this is a clear regression.

---------

Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times D-Complex Quite challenging from either a design or technical perspective. Ask for help! S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

5 participants