Aevyrie runs a Bevy scene with 10 million entities and a physics simulation at 120fps. Despawning a single entity takes 2ms. A frame budget at 120fps is about 8ms, so one despawn eats a quarter of it. Three despawns per frame and you start dropping frames.
They filed a bug with a profiling screenshot and a two-word title: "Extremely slow entity despawning."
Then they fixed it themselves.
The PR
bevyengine/bevy#23297 — "Optimize entity removal by scanning from the end"
Bevy is an ECS game engine written in Rust. Every object in a game (a character, a bullet, a particle) is an entity with components attached. Entities can have parent-child relationships: a character has children for arms, weapons, particle effects. Bevy stores those children in a Vec<Entity>:
pub struct Children(Vec<Entity>);When you despawn entity B, Bevy removes it from its parent's children list. The remove function scans forward from the start to find it:
fn remove(&mut self, entity: Entity) -> bool {
if let Some(index) = <[Entity]>::iter(self)
.position(|e| *e == entity) {
Vec::remove(self, index);
return true;
}
false
}.position() starts at index 0 and walks forward. But entities get appended to the end of the Vec when they're created. The oldest ones sit at the front. The newest sit at the back.
The entities that get despawned most often are the new ones — projectiles, particles, temporary effects. In Aevyrie's scene, every removal scanned past millions of long-lived static entities before finding a recently-added one near the tail.
The fix:
fn remove(&mut self, entity: Entity) -> bool {
// Scan from the back. Recently added entities live at the
// tail and are more likely to be despawned.
if let Some(index) = <[Entity]>::iter(self)
.rposition(|e| *e == entity) {
Vec::remove(self, index);
return true;
}
false
}.position() became .rposition(). Start from the end instead of the beginning.
Aevyrie posted benchmarks from the same 10-million-entity scene. Despawning disappeared from the profiling traces entirely. Roughly 1,000x faster. They added it to Bevy's next release milestone and called it "a free win and tiny change."
One reviewer: "When an entity is a child for a long time, it becomes less and less likely to be a subject of removal at the parent, compared to younger children."
What the dependency graph shows
The changed file is relationship_source_collection.rs. It defines a trait called RelationshipSourceCollection, the interface for how entity collections work: add, remove, iterate, clear. Vec<Entity> is one of several types that implement it, and it's the one backing Children.
Children is the core parent-child relationship in Bevy. Scene graphs, UI trees, transform propagation, animation rigs all use it.
From there the dependency chain goes:
relationship_source_collection.rs→ the relationship module- The relationship module →
hierarchy.rs, which definesChildrenandChildOf hierarchy.rs→bevy_ecs- 46 crates depend on
bevy_ecs: rendering, transforms, UI, audio, animation, sprites, lighting, picking, gizmos, cameras, scenes
The explore graph has 1,714 files. One file changed. 1,586 sit in the blast radius.

Select the changed file and you can watch the blast radius spread from ECS internals into 46 different crates.
The bigger picture
Aevyrie also opened a second PR (#23296) that replaces the Vec with an EntityIndexSet for O(1) lookups. That one changes 6 files and is still open. The two-line fix landed first because it was safe and solved the immediate problem.
The Vec<Entity> linear scan worked fine for years. It only became a problem when 10 million long-lived entities made starting from the wrong end expensive. It's still a linear scan. But new entities get removed first, and new entities live at the tail, so scanning backwards finds them immediately.
The RelationshipSourceCollection trait is implemented once and called from everywhere. One scan direction change, 1,586 files downstream.
You can explore any public GitHub PR with CodeLayers Explore — paste a URL and get an interactive 3D dependency graph. The VS Code extension shows blast radius inline as you code. The GitHub Action posts visualization links on PRs automatically.
Blast Radius is a weekly series where we look at real pull requests through 3D dependency graphs. Got a PR you want us to look at? Find us on Bluesky.
