Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accelerate Query::get #12925

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open

Accelerate Query::get #12925

wants to merge 20 commits into from

Conversation

re0312
Copy link
Contributor

@re0312 re0312 commented Apr 11, 2024

Objective

  • query::get is commonly used to retrieve an item from a specific entity. However, its performance may not be optimal when dealing with a group of entities, particularly for wide queries. In fact, it may even be slower than EntityHashmap.

Solution

  • In real-world scenarios, it's highly probable that the next entity belongs to the same archetype as the previous one , Therefore, caching the fetch operation can lead to faster retrieval.

Changelog

  • Added QueryEntityGetter
  • Added Query::getter(_mut)
  • Added QueryState::getter(_mut)

Performance

  • Best case (all entity in the same archetype), 2x-4x faster with QueryEntityGetter::get , 4x-10x faster with QueryEntityGetter::get_unchecked .
  • Worst case (every entity with different archetypes and using safety get ), it has 10%~20% regresion.

image

@re0312 re0312 changed the title Accelarate Query::get Accelerate Query::get Apr 11, 2024
@SolarLiner SolarLiner added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times labels Apr 11, 2024
@alice-i-cecile alice-i-cecile added the M-Needs-Release-Note Work that should be called out in the blog due to impact label Apr 11, 2024
}

/// Return a [`QueryEntityGetter`] to get the query item for the [`Entity`].
/// this could be more efficient than [`Query::get`].
Copy link
Member

Choose a reason for hiding this comment

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

This should call out why / when you might expect this to be faster.


use super::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState};

/// A getter used for fast fetch its Component with Specific [`Entity`]
Copy link
Member

Choose a reason for hiding this comment

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

This doc string needs an editing pass.

Copy link
Member

@james7132 james7132 left a comment

Choose a reason for hiding this comment

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

Thanks for taking a swing at this! Haven't had the time for a full review yet, but a bit of a bikeshed on the name QueryEntityGetter -> PointQuery.

This should be done in a followup PR, but PointQuery implementing SystemParam would also be useful, though it may not be intuitive to use since immutable fetches require a mutable struct.

@re0312
Copy link
Contributor Author

re0312 commented Apr 12, 2024

Thanks for taking a swing at this! Haven't had the time for a full review yet, but a bit of a bikeshed on the name QueryEntityGetter -> PointQuery.

This should be done in a followup PR, but PointQuery implementing SystemParam would also be useful, though it may not be intuitive to use since immutable fetches require a mutable struct.

I did a benchmark on many_foxes with this PR, and I didn't observe any performance differences in propagate_transform. Only the use of the unsafe version seemed to yield around a 10% improvement.

TBH, the greatest value of this PR lies in providing a more versatile method for retrieving items from many entities than iter_many. However, I'm not certain if this approach is worthy.

crates/bevy_ecs/src/query/getter.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/query/state.rs Outdated Show resolved Hide resolved
@james7132
Copy link
Member

Might want to test this against the visibility propagation system since that runs single-threaded and thus can needing fetch reinitialization as often as the transform propagate system.

@re0312
Copy link
Contributor Author

re0312 commented Apr 14, 2024

Might want to test this against the visibility propagation system since that runs single-threaded and thus can needing fetch reinitialization as often as the transform propagate system.

run many_foxes with all entities Visibility changed every frame,
system{name="bevy_render::view::visibility::visibility_propagate_system"}

from 400us to 240 us
image

benches/benches/bevy_ecs/world/mod.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/entity/mod.rs Outdated Show resolved Hide resolved
.entities
.get(entity)
.ok_or(QueryEntityError::NoSuchEntity(entity))?;
if !D::IS_DENSE || self.last_archetype_id != location.archetype_id {
Copy link
Member

Choose a reason for hiding this comment

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

This is unsound. Please check how the dense checks are done elsewhere. If D::IS_DENSE and F::IS_DENSE, this should use tables and table IDs instead of archetypes.

crates/bevy_ecs/src/query/state.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/system/query.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/system/query.rs Outdated Show resolved Hide resolved
///
/// This is always guaranteed to run in `O(1)` time.
#[inline]
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError> {
pub fn get(&mut self, entity: Entity) -> Result<D::Item<'_>, QueryEntityError> {

For this to be sound, we cannot allow this borrow to live longer than the borrow on self.

/// `entity` must on the same `World` that the `Query` was generated.
/// `entity` must match the `Query` that generate this getter.
#[inline]
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'w> {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'w> {
pub unsafe fn get_unchecked(&mut self, entity: Entity) -> D::Item<'_> {

re0312 and others added 2 commits April 14, 2024 21:17
Co-authored-by: James Liu <contact@jamessliu.com>
@re0312 re0312 requested a review from james7132 April 15, 2024 02:15
@janhohenheim janhohenheim added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jul 17, 2024
Copy link
Contributor

@ItsDoot ItsDoot left a comment

Choose a reason for hiding this comment

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

Noticed a couple things.

pub fn get(&mut self, entity: Entity) -> Result<D::Item<'_>, QueryEntityError> {
// SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict
unsafe { std::mem::transmute(self.get_unsafe(entity)) }
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
unsafe { std::mem::transmute(self.get_unsafe(entity)) }
let result = unsafe { self.get_unsafe(entity) };
match result {
Ok(item) => Ok(D::shrink(item)),
Err(err) => Err(err),
}

We can use WorldQuery::shrink instead of a transmute.

}

let item = D::fetch(&mut self.fetch, entity, location.table_row);
std::mem::transmute(item)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
std::mem::transmute(item)
D::shrink(item)

We can use shrink here as well, instead of the transmute.

@BenjaminBrienen BenjaminBrienen added 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 Oct 31, 2024
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 M-Needs-Release-Note Work that should be called out in the blog due to impact S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants