-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Optimize Entities::reserve_entity to reduce memory and atomics
#658
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
Conversation
TL;DR: this cuts in half the number of atomic ops per
`Entities::reserve_entity` and also cuts in half the memory
overhead related to Entity freelisting and allocation.
Entities maintained two tables: `reserved` and `free`, each
with one slot for every possible Entity. This way every entity could
be freelisted or reserved without needing more memory. Combined,
these tables took 2*meta.len() slots.
The key observation is that any entity can only be in one table or the
other, but not both, so we actually only need meta.size() slots total,
i.e. half the memory. One way to take advantage of this would be to
have the freelist grow "up" from the bottom of a Vec, and reserved
grow "down" from the top of the same Vec, because we know they
can't meet in the middle. But there is a better way.
This change combines `free` and `reserved` into `unused`, cutting the
memory in half, and also lays them out like this:
```
------------------------------------------
| freelist | reserved | <room to expand> |
------------------------------------------
^ ^
freelist_cursor reserved_cursor
```
When `reserve_entity` wants to move something from the freelist
to the reserved list, it simply atomically decrements `freelist_cursor`,
the boundary between the two, without moving any array entries.
This avoids the previous atomic operation to update `reserved`.
`reserved_cursor` no longer needs to be atomic because it is only
modified by `&mut` methods.
This approach does make `alloc()` and `free()` slightly more complicated.
`alloc()` has to update `reserved_cursor` to match `freelist_cursor`
(just a single store). This works if you can't have unflushed data
when you alloc, which is my understanding.
If `free` tries to push an item and there are reserved values, it
needs to move the first reserved value to the end of the reserved
range to make room for the larger freelist. I don't know if this can
happen -- is it actually legal to free into an unflushed Entities?
If not, then we don't need some of this code.
This change also has a few other tweaks:
- When grow() dumps copies values into the freelist, put them "before"
the old freelist values (if any), so they get popped last. This way
we are more likely to hand out lower IDs, only allocating higher IDs
when we really need them.
- Added some unit tests.
I wasn't completely clear on the `reserved_len` (etc.) code and its
requirements (what is legal while iterating with those?), so I took my
best guess how to update it.
|
I do love optimizations :) Given that this touches hecs code, it might also be a good idea to submit a PR to the hecs repo so they can get these optimizations too. @Ralith is this something you're interested in? It would be nice to get your feedback on this too given that you wrote |
|
Another idea: if it's not legal to free() while unflushed stuff exists (unclear to me), the |
|
Another thing that becomes possible by not moving freelist values around is that it's easy to add a |
|
These changes (subject to detailed review) would be very welcome in hecs!
I'm not certain what you mean by "into" here. |
|
Thanks @Ralith, that's the strengthened requirement I was interested in. I'll put together a PR for hecs and we can talk more about it there. |
|
I will submit something upstream, so withdrawing this PR. |
|
Cool cool. If/when this gets merged there, feel free to open up another pr here. |
|
Upstream PR is Ralith/hecs#86 |
|
The first thing that comes to my mind perfwise is the inlining improvements that were made in bevy which haven't yet been upstreamed. Help getting that synced back up would be great! |
Updates the requirements on [petgraph](https://github.com/petgraph/petgraph) to permit the latest version. <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/petgraph/petgraph/blob/master/RELEASES.rst">petgraph's changelog</a>.</em></p> <blockquote> <h1>Version 0.7.1 (2025-01-08)</h1> <ul> <li>Do not unnecessarily restrict <code>indexmap</code> version (<code>[#714](https://github.com/petgraph/petgraph/issues/714)</code>_)</li> <li>Export <code>UndirectedAdaptor</code> (<code>[#717](https://github.com/petgraph/petgraph/issues/717)</code>_)</li> </ul> <p>.. _<code>[#714](https://github.com/petgraph/petgraph/issues/714)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/714">petgraph/petgraph#714</a> .. _<code>[#717](https://github.com/petgraph/petgraph/issues/717)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/717">petgraph/petgraph#717</a></p> <h1>Version 0.7.0 (2024-12-31)</h1> <ul> <li>Re-released version 0.6.6 with the correct version number, as it included a major update to an exposed crate (<code>[#664](https://github.com/petgraph/petgraph/issues/664)</code>_).</li> </ul> <h1>Version 0.6.6 (2024-12-31 - yanked)</h1> <ul> <li>Add graph6 format encoder and decoder (<code>[#658](https://github.com/petgraph/petgraph/issues/658)</code>_)</li> <li>Dynamic Topological Sort algorithm support (<code>[#675](https://github.com/petgraph/petgraph/issues/675)</code>_)</li> <li>Add <code>UndirectedAdaptor</code> (<code>[#695](https://github.com/petgraph/petgraph/issues/695)</code>_)</li> <li>Add <code>LowerHex</code> and <code>UpperHex</code> implementations for <code>Dot</code> (<code>[#687](https://github.com/petgraph/petgraph/issues/687)</code>_)</li> <li>Make <code>serde</code> support more complete (<code>[#550](https://github.com/petgraph/petgraph/issues/550)</code>_)</li> <li>Process multiple edges in the Floyd-Warshall implementation (<code>[#685](https://github.com/petgraph/petgraph/issues/685)</code>_)</li> <li>Update <code>fixedbitset</code> to 0.5.7 (<code>[#664](https://github.com/petgraph/petgraph/issues/664)</code>_)</li> <li>Fix <code>immediately_dominated_by</code> function called on root of graph returns root itself (<code>[#670](https://github.com/petgraph/petgraph/issues/670)</code>_)</li> <li>Fix adjacency matrix for <code>Csr</code> and <code>List</code> (<code>[#648](https://github.com/petgraph/petgraph/issues/648)</code>_)</li> <li>Fix clippy warnings (<code>[#701](https://github.com/petgraph/petgraph/issues/701)</code>_)</li> <li>Add performance note to the <code>all_simple_paths</code> function documentation (<code>[#693](https://github.com/petgraph/petgraph/issues/693)</code>_)</li> </ul> <p>.. _<code>[#658](https://github.com/petgraph/petgraph/issues/658)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/658">petgraph/petgraph#658</a> .. _<code>[#675](https://github.com/petgraph/petgraph/issues/675)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/675">petgraph/petgraph#675</a> .. _<code>[#695](https://github.com/petgraph/petgraph/issues/695)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/695">petgraph/petgraph#695</a> .. _<code>[#687](https://github.com/petgraph/petgraph/issues/687)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/687">petgraph/petgraph#687</a> .. _<code>[#550](https://github.com/petgraph/petgraph/issues/550)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/550">petgraph/petgraph#550</a> .. _<code>[#685](https://github.com/petgraph/petgraph/issues/685)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/685">petgraph/petgraph#685</a> .. _<code>[#664](https://github.com/petgraph/petgraph/issues/664)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/664">petgraph/petgraph#664</a> .. _<code>[#670](https://github.com/petgraph/petgraph/issues/670)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/670">petgraph/petgraph#670</a> .. _<code>[#648](https://github.com/petgraph/petgraph/issues/648)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/648">petgraph/petgraph#648</a> .. _<code>[#701](https://github.com/petgraph/petgraph/issues/701)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/701">petgraph/petgraph#701</a> .. _<code>[#693](https://github.com/petgraph/petgraph/issues/693)</code>: <a href="https://redirect.github.com/petgraph/petgraph/pull/693">petgraph/petgraph#693</a></p> <h1>Version 0.6.5 (2024-05-06)</h1> <ul> <li>Add rayon support for <code>GraphMap</code> (<code>[#573](https://github.com/petgraph/petgraph/issues/573)</code><em>, <code>[#615](https://github.com/petgraph/petgraph/issues/615)</code></em>)</li> <li>Add <code>Topo::with_initials</code> method (<code>[#585](https://github.com/petgraph/petgraph/issues/585)</code>_)</li> <li>Add logo to the project (<code>[#598](https://github.com/petgraph/petgraph/issues/598)</code>_)</li> <li>Add Ford-Fulkerson algorithm (<code>[#640](https://github.com/petgraph/petgraph/issues/640)</code>_)</li> <li>Update <code>itertools</code> to 0.12.1 (<code>[#628](https://github.com/petgraph/petgraph/issues/628)</code>_)</li> <li>Update <code>GraphMap</code> to allow custom hash functions (<code>[#622](https://github.com/petgraph/petgraph/issues/622)</code>_)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/petgraph/petgraph/commit/2765d2a55044a35a20d95c50a2f318fbc3bb85f4"><code>2765d2a</code></a> Release 0.7.1 (<a href="https://redirect.github.com/petgraph/petgraph/issues/722">#722</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/d341db9d18582cfcffebf320896947e55ecba09c"><code>d341db9</code></a> ci: downgrade hashbrown rather than limiting indexmap (<a href="https://redirect.github.com/petgraph/petgraph/issues/714">#714</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/73c64b629f38dc8b0a8edc7550f16a662ed05c25"><code>73c64b6</code></a> Make UndirectedAdaptor & inner G pub (<a href="https://redirect.github.com/petgraph/petgraph/issues/717">#717</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/d057429081bc02812d3605d1e7159f0e73361e51"><code>d057429</code></a> Release <code>0.7.0</code> (<a href="https://redirect.github.com/petgraph/petgraph/issues/713">#713</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/13ebd7d2ddd4a1ac07a33606c0d4f82d342e5fa6"><code>13ebd7d</code></a> Release <code>0.6.6</code> (<a href="https://redirect.github.com/petgraph/petgraph/issues/706">#706</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/159341e4af2a1292d8a1da428d64784e2dfc8ae5"><code>159341e</code></a> Implement DSatur graph coloring algorithm</li> <li><a href="https://github.com/petgraph/petgraph/commit/7fa3aac97168de7fca54644a5b45c464d5245535"><code>7fa3aac</code></a> fix: adjacency matrix for csr and adjacency list (<a href="https://redirect.github.com/petgraph/petgraph/issues/648">#648</a>)</li> <li><a href="https://github.com/petgraph/petgraph/commit/9fda6bbe2e52663d03317083d2623faa4f0d4cd4"><code>9fda6bb</code></a> Update gitignore with possible editor extensions to ensure they do not occur ...</li> <li><a href="https://github.com/petgraph/petgraph/commit/9b5837e395c342e9cb2e5a2b3870fa7ee6650ef4"><code>9b5837e</code></a> Allow clippy::needless_range_loop in benches</li> <li><a href="https://github.com/petgraph/petgraph/commit/ad9f83c2ae237ba6f5832a19f01ef8c3ae14dd19"><code>ad9f83c</code></a> Process warnings in 'test' target</li> <li>Additional commits viewable in <a href="https://github.com/petgraph/petgraph/compare/[email protected]@v0.7.1">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
TL;DR: this cuts in half the number of atomic ops per
Entities::reserve_entityand also cuts in half the memoryoverhead related to Entity freelisting and allocation.
Entities maintained two tables:
reservedandfree, eachwith one slot for every possible Entity. This way every entity could
be freelisted or reserved without needing more memory. Combined,
these tables took 2*meta.len() slots.
The key observation is that any entity can only be in one table or the
other, but not both, so we actually only need meta.size() slots total,
i.e. half the memory. One way to take advantage of this would be to
have the freelist grow "up" from the bottom of a Vec, and reserved
grow "down" from the top of the same Vec, because we know they
can't meet in the middle. But there is a better way.
This change combines
freeandreservedintounused, cutting thememory in half, and also lays them out like this:
When
reserve_entitywants to move something from the freelistto the reserved list, it simply atomically decrements
freelist_cursor,the boundary between the two, without moving any array entries.
This avoids the previous atomic operation to update
reserved.reserved_cursorno longer needs to be atomic because it is onlymodified by
&mutmethods.This approach does make
alloc()andfree()slightly more complicated.alloc()has to updatereserved_cursorto matchfreelist_cursor(just a single store). This works if you can't have unflushed data
when you alloc, which is my understanding.
If
freetries to push an item and there are reserved values, itneeds to move the first reserved value to the end of the reserved
range to make room for the larger freelist. I don't know if this can
happen -- is it actually legal to free into an unflushed Entities?
If not, then we don't need some of this code.
This change also has a few other tweaks:
When grow() copies values into the freelist, put them "before"
the old freelist values (if any), so they get popped last. This way
we are more likely to hand out lower IDs, only allocating higher IDs
when we really need them.
Added some unit tests.
I wasn't completely clear on the
reserved_len(etc.) code and itsrequirements (what is legal while iterating with those?), so I took my
best guess how to update it.