Skip to content

Commit 09cfe1f

Browse files
committed
store key in the lower bits of the string length
* Optimizes for calling `.data()`
1 parent b43cc04 commit 09cfe1f

File tree

5 files changed

+78
-19
lines changed

5 files changed

+78
-19
lines changed

benches/benches/bevy_ecs/scheduling/schedule.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ pub fn build_schedule(criterion: &mut Criterion) {
6464
// Use multiple different kinds of label to ensure that dynamic dispatch
6565
// doesn't somehow get optimized away.
6666
#[derive(Debug, Clone, Copy)]
67-
struct NumLabel(usize);
67+
struct NumLabel(u16);
6868
#[derive(Debug, Clone, Copy, SystemLabel)]
6969
struct DummyLabel;
7070

7171
impl SystemLabel for NumLabel {
72-
fn data(&self) -> u64 {
73-
self.0 as u64
72+
fn data(&self) -> u16 {
73+
self.0
7474
}
7575
fn as_str(&self) -> &'static str {
7676
let s = self.0.to_string();
@@ -88,7 +88,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
8888
// Also, we are performing the `as_label` operation outside of the loop since that
8989
// requires an allocation and a leak. This is not something that would be necessary in a
9090
// real scenario, just a contrivance for the benchmark.
91-
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i).as_label()).collect();
91+
let labels: Vec<_> = (0..1000).map(|i| NumLabel(i as u16).as_label()).collect();
9292

9393
// Benchmark graphs of different sizes.
9494
for graph_size in [100, 500, 1000] {

crates/bevy_ecs/src/schedule/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ impl RunCriteriaLabel for DriverLabel {
5858
fn type_id(&self) -> core::any::TypeId {
5959
self.0
6060
}
61-
fn data(&self) -> u64 {
61+
fn data(&self) -> u16 {
6262
0
6363
}
6464
fn as_str(&self) -> &'static str {

crates/bevy_ecs/src/system/function_system.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ impl<T: 'static> SystemLabel for SystemTypeIdLabel<T> {
458458
std::any::type_name::<T>()
459459
}
460460
#[inline]
461-
fn data(&self) -> u64 {
461+
fn data(&self) -> u16 {
462462
0
463463
}
464464
}

crates/bevy_macro_utils/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ pub fn derive_label(
191191
let mut path = syn::Path::from(ident.clone());
192192
path.segments.push(v.ident.clone().into());
193193

194-
let i = i as u64;
194+
// hopefully no one has more than 60 thousand variants
195+
let i = i as u16;
195196
data_arms.push(quote! { #path { .. } => #i });
196197

197198
let lit = format!("{ident}::{}", v.ident.clone());
@@ -227,7 +228,7 @@ pub fn derive_label(
227228

228229
(quote! {
229230
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
230-
fn data(&self) -> u64 {
231+
fn data(&self) -> u16 {
231232
#data
232233
}
233234
fn as_str(&self) -> &'static str {

crates/bevy_utils/src/label.rs

+69-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::{
44
any::Any,
55
hash::{Hash, Hasher},
6+
marker::PhantomData,
67
};
78

89
pub trait DynEq: Any {
@@ -47,6 +48,66 @@ where
4748
}
4849
}
4950

51+
#[doc(hidden)]
52+
/// A string slice, stuffed with a payload of data `16` bits long.
53+
/// This means that the maximum length of the string is `2^(size-16)`,
54+
/// where `size` is either 32 or 64 depending on your computer.
55+
#[derive(Clone, Copy)]
56+
pub struct StuffedStr<'a> {
57+
ptr: *const u8,
58+
marker: PhantomData<&'a str>,
59+
60+
/// lower 16 bits stores the data,
61+
/// upper 48 or 16 bits stores the length of the string.
62+
/// This layout optimizes for calling `.data()`
63+
meta: usize,
64+
}
65+
66+
unsafe impl<'a> Send for StuffedStr<'a> {}
67+
unsafe impl<'a> Sync for StuffedStr<'a> {}
68+
69+
impl<'a> StuffedStr<'a> {
70+
const DATA_MASK: usize = !Self::LEN_MASK;
71+
const LEN_MASK: usize = !0 << 16;
72+
73+
pub fn new(str: &'a str, data: u16) -> Self {
74+
#[cold]
75+
#[inline(never)]
76+
fn panic() -> ! {
77+
// needs a better message but its late
78+
panic!("string exceeds {} bytes", StuffedStr::LEN_MASK >> 16);
79+
}
80+
81+
let ptr = str.as_ptr();
82+
// Make sure there's enough room to store the data.
83+
if str.len().leading_zeros() < 16 {
84+
panic();
85+
}
86+
let meta = data as usize | str.len() << 16;
87+
Self {
88+
ptr,
89+
marker: PhantomData,
90+
meta,
91+
}
92+
}
93+
94+
fn len(&self) -> usize {
95+
self.meta >> 16
96+
}
97+
pub fn data(&self) -> u16 {
98+
let data = self.meta & Self::DATA_MASK;
99+
data as u16
100+
}
101+
pub fn as_str(&self) -> &'a str {
102+
// SAFETY: this instance was constructed from a string slice of length `len`, and lifetime `'a`.
103+
// It is sound to convert it back to a slice.
104+
unsafe {
105+
let slice = std::slice::from_raw_parts(self.ptr, self.len());
106+
std::str::from_utf8_unchecked(slice)
107+
}
108+
}
109+
}
110+
50111
/// Macro to define a new label trait
51112
///
52113
/// # Example
@@ -71,11 +132,7 @@ macro_rules! define_label {
71132
) => {
72133
$(#[$id_attr])*
73134
#[derive(Clone, Copy)]
74-
pub struct $id_name {
75-
ty: ::core::any::TypeId,
76-
data: u64,
77-
text: &'static str
78-
}
135+
pub struct $id_name(::core::any::TypeId, $crate::label::StuffedStr<'static>);
79136

80137
impl ::core::fmt::Debug for $id_name {
81138
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
@@ -90,14 +147,15 @@ macro_rules! define_label {
90147
let ty = self.type_id();
91148
let data = self.data();
92149
let text = self.as_str();
93-
$id_name { ty, data, text }
150+
let stuffed = $crate::label::StuffedStr::new(text, data);
151+
$id_name(ty, stuffed)
94152
}
95153
/// Returns the [`TypeId`] used to differentiate labels.
96154
fn type_id(&self) -> ::core::any::TypeId {
97155
::core::any::TypeId::of::<Self>()
98156
}
99157
/// Returns a number used to distinguish different labels of the same type.
100-
fn data(&self) -> u64;
158+
fn data(&self) -> u16;
101159
/// Returns the representation of this label as a string literal.
102160
///
103161
/// In cases where you absolutely need a label to be determined at runtime,
@@ -110,13 +168,13 @@ macro_rules! define_label {
110168
*self
111169
}
112170
fn type_id(&self) -> ::core::any::TypeId {
113-
self.ty
171+
self.0
114172
}
115-
fn data(&self) -> u64 {
116-
self.data
173+
fn data(&self) -> u16 {
174+
self.1.data()
117175
}
118176
fn as_str(&self) -> &'static str {
119-
self.text
177+
self.1.as_str()
120178
}
121179
}
122180

0 commit comments

Comments
 (0)