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

Allow Option<Entity> to use memory layout optimization #3022

Closed
Tracked by #3742
notverymoe opened this issue Oct 25, 2021 · 7 comments
Closed
Tracked by #3742

Allow Option<Entity> to use memory layout optimization #3022

notverymoe opened this issue Oct 25, 2021 · 7 comments
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible C-Performance A change motivated by improving speed, memory usage or compile times

Comments

@notverymoe
Copy link
Contributor

What problem does this solve or what need does it fill?

Entity currently can't be default constructed or represent a "never valid" entity. Which is extremely useful for sparse relational data structures like grids and oct/quadtrees. This is easily sidestepped via Option<Entity>, as rust does - so there's not a hard limit here or anything of course.

The issue is that Option will introduce some memory overhead, which can become significant in flatter data structures like grids.

What solution would you like?

I'd like to be allow the compiler to leverage memory layout optimization, to remove the storage overhead of Option<Entity>. This could be done by changing either id or generation to a NonZeroU32. I'd propose that generation should be the one considered for the change, as it has less public API surface.

What alternative(s) have you considered?

  • Use raw entity IDs without the generation and accept the risk of reused ids.
  • Take the bit representation of Entity, increment it for storage as an wrapped NonZeroU64 and then decrement it to convert it back.
  • Accept the overhead of the Option
  • Use less sparse/flat/smarter representations for relational data (ie. chunks)

Additional context

In my particular case, I have "TileCoord" component and a "MultiTileCoord" component that create a relation between entities and a particular grid square. On top of that, I maintain a fixed-size grid structure that is updated as TileCoords are altered to accelerate neighbourhood queries and generate certain events (ie. neighbour changed, tile moved ontop). As a result, some of the grid squares would be empty which can't be represented with Entity alone, so I need to utilize Option<Entity> and incur the storage overhead.

On an example 1-layer grid of 256x256, this is an overhead of about ~256KiB. This isn't a pressing issue in my case, since I'm currently using the 2nd alternative I outlined. I just thought it might be a nice space optimization to put into core.

@notverymoe notverymoe added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Oct 25, 2021
@DJMcNab
Copy link
Member

DJMcNab commented Oct 25, 2021

I know that there's an open pull request for this, but GitHub's search is so terrible that I cannot find it

@notverymoe
Copy link
Contributor Author

notverymoe commented Oct 25, 2021

The only semi-related one I was able to find was relations: https://github.com/BoxyUwU/rfcs/blob/min-relations/rfcs/min-relations.md

But I'll keep looking!

@DJMcNab
Copy link
Member

DJMcNab commented Oct 25, 2021

It's #2104

@notverymoe
Copy link
Contributor Author

Seems like a different issue, I'm specifically dealing with the representation of Entity, which that PR doesn't appear to touch

@NiklasEi NiklasEi added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times and removed S-Needs-Triage This issue needs to be labelled labels Oct 25, 2021
@notverymoe
Copy link
Contributor Author

#1395 contains some comments pertaining to this issue

@notverymoe
Copy link
Contributor Author

notverymoe commented Oct 26, 2021

Currently working on this here:
https://github.com/nvm-contrib/bevy/tree/option-entity-generation-optimization

Current implementation:

  • Changes generation to a NonZeroU32
  • Changes Entity::from_bits to return Option<Entity> [API break]
  • Updated broken tests (mostly just the generation initialization)
  • Adds some tests to check generation wrapping behaviour, from_bits and that Option<Entity> gets niche optimization.

Currently just looking for fires/potential issues with this solution. One potential issue is that incrementing generations is more costly, as we need to do it checked and manually wrap to 1 - haven't profiled that yet though.

The Entity::from_bits breakage happens because it's possible that a u64 might have an invalid generation of 0 and panic-ing to maintain the API is probably a bad move. It does now match the WGPU functions of the same name though which is, at least, mildly nice.

Edit: One fire mentioned by bd on discord (apologies, I don't know their GitHub handle) would be the reflect crate. Looking into that now. Edit2: Just heard back, apparently generation isn't reflected, so that should be fine

github-merge-queue bot pushed a commit that referenced this issue Jan 8, 2024
…on (#9907)

# Objective

- Implements change described in
#3022
- Goal is to allow Entity to benefit from niche optimization, especially
in the case of Option<Entity> to reduce memory overhead with structures
with empty slots

## Discussion
- First PR attempt: #3029
- Discord:
https://discord.com/channels/691052431525675048/1154573759752183808/1154573764240093224

## Solution

- Change `Entity::generation` from u32 to NonZeroU32 to allow for niche
optimization.
- The reason for changing generation rather than index is so that the
costs are only encountered on Entity free, instead of on Entity alloc
- There was some concern with generations being used, due to there being
some desire to introduce flags. This was more to do with the original
retirement approach, however, in reality even if generations were
reduced to 24-bits, we would still have 16 million generations available
before wrapping and current ideas indicate that we would be using closer
to 4-bits for flags.
- Additionally, another concern was the representation of relationships
where NonZeroU32 prevents us using the full address space, talking with
Joy it seems unlikely to be an issue. The majority of the time these
entity references will be low-index entries (ie. `ChildOf`, `Owes`),
these will be able to be fast lookups, and the remainder of the range
can use slower lookups to map to the address space.
- It has the additional benefit of being less visible to most users,
since generation is only ever really set through `from_bits` type
methods.
- `EntityMeta` was changed to match
- On free, generation now explicitly wraps:
- Originally, generation would panic in debug mode and wrap in release
mode due to using regular ops.
- The first attempt at this PR changed the behavior to "retire" slots
and remove them from use when generations overflowed. This change was
controversial, and likely needs a proper RFC/discussion.
- Wrapping matches current release behaviour, and should therefore be
less controversial.
- Wrapping also more easily migrates to the retirement approach, as
users likely to exhaust the exorbitant supply of generations will code
defensively against aliasing and that defensive code is less likely to
break than code assuming that generations don't wrap.
- We use some unsafe code here when wrapping generations, to avoid
branch on NonZeroU32 construction. It's guaranteed safe due to how we
perform wrapping and it results in significantly smaller ASM code.
    - https://godbolt.org/z/6b6hj8PrM 

## Migration

- Previous `bevy_scene` serializations have a high likelihood of being
broken, as they contain 0th generation entities.

## Current Issues
 
- `Entities::reserve_generations` and `EntityMapper` wrap now, even in
debug - although they technically did in release mode already so this
probably isn't a huge issue. It just depends if we need to change
anything here?

---------

Co-authored-by: Natalie Baker <[email protected]>
@nicopap
Copy link
Contributor

nicopap commented Jan 29, 2024

Fixed by #9907

@nicopap nicopap closed this as completed Jan 29, 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-Feature A new feature, making something new possible C-Performance A change motivated by improving speed, memory usage or compile times
Projects
None yet
Development

No branches or pull requests

4 participants