Skip to content

ECS: put strings only used for debug behind a feature #19558

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

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

Conversation

mockersf
Copy link
Member

@mockersf mockersf commented Jun 9, 2025

Objective

  • Many strings in bevy_ecs are created but only used for debug: system name, component name, ...
  • Those strings make a significant part of the final binary and are no use in a released game

Solution

  • Use strings to find ... strings in a binary
  • Try to find where they come from
  • Many are made from type_name::<T>() and only used in error / debug messages
  • Add a new structure DebugName that holds no value if debug feature is disabled
  • Replace core::any::type_name::<T>() by DebugName::type_name::<T>()

Testing

Measurements were taken without the new feature being enabled by default, to help with commands

File Size

I tried building the breakout example with cargo run --release --example breakout

debug enabled debug disabled
81621776 B 77735728B
77.84MB 74.13MB

Compilation time

hyperfine --min-runs 15 --prepare "cargo clean && sleep 5" 'RUSTC_WRAPPER="" cargo build --release --example breakout' 'RUSTC_WRAPPER="" cargo build --release --example breakout --features debug'

breakout' 'RUSTC_WRAPPER="" cargo build --release --example breakout --features debug'
Benchmark 1: RUSTC_WRAPPER="" cargo build --release --example breakout
  Time (mean ± σ):     84.856 s ±  3.565 s    [User: 1093.817 s, System: 32.547 s]
  Range (min … max):   78.038 s … 89.214 s    15 runs

Benchmark 2: RUSTC_WRAPPER="" cargo build --release --example breakout --features debug
  Time (mean ± σ):     92.303 s ±  2.466 s    [User: 1193.443 s, System: 33.803 s]
  Range (min … max):   90.619 s … 99.684 s    15 runs

Summary
  RUSTC_WRAPPER="" cargo build --release --example breakout ran
    1.09 ± 0.05 times faster than RUSTC_WRAPPER="" cargo build --release --example breakout --features debug

@mockersf mockersf requested a review from alice-i-cecile June 9, 2025 17:02
@mockersf mockersf added A-ECS Entities, components, systems, and events X-Contentious There are nontrivial implications that should be thought through labels Jun 9, 2025
@alice-i-cecile alice-i-cecile requested a review from inodentry June 9, 2025 17:06
@alice-i-cecile alice-i-cecile added 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 Jun 9, 2025
@alice-i-cecile
Copy link
Member

I don't understand your table there 🤔 Why are there two rows?

@mockersf
Copy link
Member Author

mockersf commented Jun 9, 2025

I don't understand your table there 🤔 Why are there two rows?

bytes and megabytes!

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Hmm. I don't love the increased feature flagging, but I think that this is a reasonable optimization to make, especially in the context of games being deployed to web.

The strategy looks reasonable to me: top-level flag, trickling down, on-by-default.

I'm sure there's more places we could use this, but I'm happy with this as a start.

Copy link
Contributor

@mgi388 mgi388 left a comment

Choose a reason for hiding this comment

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

To me the approach does look useful. I left some comments which I hope are helpful. I think I'll leave it to others to given approval though.

@@ -85,6 +85,8 @@ critical-section = [

hotpatching = ["dep:subsecond"]

debug = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth a comment describing the scope of debug? It reminds me of the the old if environment == dev checks in code which can become grab bags of everything.

For example in my game I have an entity_names feature:

commands.spawn((
  #[cfg(feature = "entity_names")]
  Name::new("Foo"),
));

I could have used a dev or debug feature, but I wanted to separate the actual feature in case I want to turn just this feature on for release builds.

To be clear, I think the feature debug probably makes sense compared to trying to break it down into many little features, but I just wonder if there's room to describe the scope of what a future Bevy developer should expect or be allowed to use this feature for?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about doing different features for things like system_name, component_name, ... but that would make the features more complex, with more tests needed, and bigger conditions when needing both.

I think a single feature debug (or maybe with a name around diagnostics? I'm open to another name) is better

Copy link
Contributor

Choose a reason for hiding this comment

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

I personally like the name and I don't think it comes across as a grab bag. I just thought a comment here on the possible scope could be useful, but comment is non-blocking. Maybe over time it will be come clear and obvious what should and shouldn't go in this feature.

Copy link
Member

Choose a reason for hiding this comment

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

I think debug_info would be much clearer and less likely to conflict as a name.

Copy link
Contributor

Choose a reason for hiding this comment

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

What could this actually conflict with?

There is also https://doc.rust-lang.org/rustc/codegen-options/index.html#debuginfo, which could be cause for confusion, not sure though.

But yeah both sound fine to me. If I had to pick myself I'd probably go with debug, but I don't have a strong argument more than "adding _info at the end feels overly specific to how the code that it's enabling is used".

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking user defined dev-mode flags? I also think it's a clearer / narrower purpose that will allow us to more clearly decide whether or not something else belongs in this flag.

@mockersf mockersf force-pushed the remove-debug-strings branch from 91b1c22 to bf85334 Compare June 17, 2025 01:26
@mockersf
Copy link
Member Author

Switched to a new DebugName struct that wraps the feature, and using DebugName::type_name::<T>() instead of core::any::type_name::<T>().

That made the diff smaller, and made it easier to use it in more places which increased a bit the size reduction, now 3.7MB!

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 S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants