Split GenericTypeCell::get_or_insert into smaller pieces (#14865)
# Objective Based on the discussion in #14864, I wanted to experiment with the core `GenericTypeCell` type, whose `get_or_insert` method accounted for 2% of the final binary size of the `3d_scene` example. The reason for this large percentage is likely because the type is fundamental to the rest of Bevy while having 3 generic parameters (the type stored `T`, the type to retrieve `G`, and the function used to insert a new value `F`). - Acts on #14864 ## Solution - Split `get_or_insert` into smaller functions with minimised parameterisation. These new functions are private as to preserve the public facing API, but could be exposed if desired. ## Testing - Ran CI locally. - Used `cargo bloat --release --example 3d_scene -n 100000 --message-format json > out.json` and @cart's [bloat analyzer](https://gist.github.com/cart/722756ba3da0e983d207633e0a48a8ab) to measure a 428KiB reduction in binary size when compiling on Windows 10. - ~I have _not_ benchmarked to determine if this improves/hurts performance.~ See [below](https://github.com/bevyengine/bevy/pull/14865#issuecomment-2306083606). ## Notes In my opinion this seems like a good test-case for the concept of debloating generics within the Bevy codebase. I believe the performance impact here is negligible in either direction (at runtime and compile time), but the binary reduction is measurable and quite significant for a relatively minor change in code. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
parent
96f1fd73cb
commit
44620dd6ae
@ -239,22 +239,38 @@ impl<T: TypedProperty> GenericTypeCell<T> {
|
||||
G: Any + ?Sized,
|
||||
F: FnOnce() -> T::Stored,
|
||||
{
|
||||
let type_id = TypeId::of::<G>();
|
||||
self.get_or_insert_by_type_id(TypeId::of::<G>(), f)
|
||||
}
|
||||
|
||||
// Put in a separate scope, so `mapping` is dropped before `f`,
|
||||
// since `f` might want to call `get_or_insert` recursively
|
||||
// and we don't want a deadlock!
|
||||
{
|
||||
let mapping = self.0.read().unwrap_or_else(PoisonError::into_inner);
|
||||
if let Some(info) = mapping.get(&type_id) {
|
||||
return info;
|
||||
}
|
||||
/// Returns a reference to the [`TypedProperty`] stored in the cell, if any.
|
||||
///
|
||||
/// This method will then return the correct [`TypedProperty`] reference for the given type `T`.
|
||||
fn get_by_type_id(&self, type_id: TypeId) -> Option<&T::Stored> {
|
||||
self.0
|
||||
.read()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.get(&type_id)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`TypedProperty`] stored in the cell.
|
||||
///
|
||||
/// This method will then return the correct [`TypedProperty`] reference for the given type `T`.
|
||||
/// If there is no entry found, a new one will be generated from the given function.
|
||||
fn get_or_insert_by_type_id<F>(&self, type_id: TypeId, f: F) -> &T::Stored
|
||||
where
|
||||
F: FnOnce() -> T::Stored,
|
||||
{
|
||||
match self.get_by_type_id(type_id) {
|
||||
Some(info) => info,
|
||||
None => self.insert_by_type_id(type_id, f()),
|
||||
}
|
||||
}
|
||||
|
||||
let value = f();
|
||||
|
||||
let mut mapping = self.0.write().unwrap_or_else(PoisonError::into_inner);
|
||||
mapping
|
||||
fn insert_by_type_id(&self, type_id: TypeId, value: T::Stored) -> &T::Stored {
|
||||
self.0
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.entry(type_id)
|
||||
.insert({
|
||||
// We leak here in order to obtain a `&'static` reference.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user