Skip to content

Commit 8b796e8

Browse files
authored
perf(turbo-tasks-backend): use DefaultStorage for AggregationNumber to save memory (#88336)
This drops the size of the `aggregation_number` field from 16 to 12 bytes which allows the full `InnerStorage` struct to do from 136 bytes to 128 bytes (thanks alignment!) Due to allocator behavior this is probably saving more like 32 bytes per InnerStorage allocation, see [mimalloc size classes](https://github.com/microsoft/mimalloc/blob/f0cd5505aa102cee991be0367b82506638a16281/src/init.c#L60)
1 parent 90a4927 commit 8b796e8

2 files changed

Lines changed: 138 additions & 3 deletions

File tree

turbopack/crates/turbo-tasks-backend/src/backend/storage.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
AggregationNumber, CachedDataItem, CachedDataItemKey, CachedDataItemType,
1515
CachedDataItemValue, CachedDataItemValueRef, CachedDataItemValueRefMut, OutputValue,
1616
},
17-
data_storage::{AutoMapStorage, OptionStorage},
17+
data_storage::{AutoMapStorage, DefaultStorage, OptionStorage},
1818
utils::{
1919
dash_map_drop_contents::drop_contents,
2020
dash_map_multi::{RefMut, get_multiple_mut},
@@ -138,7 +138,7 @@ impl InnerStorageState {
138138
}
139139

140140
pub struct InnerStorageSnapshot {
141-
aggregation_number: OptionStorage<AggregationNumber>,
141+
aggregation_number: DefaultStorage<AggregationNumber>,
142142
output_dependent: AutoMapStorage<TaskId, ()>,
143143
output: OptionStorage<OutputValue>,
144144
upper: AutoMapStorage<TaskId, u32>,
@@ -206,7 +206,7 @@ impl InnerStorageSnapshot {
206206

207207
#[derive(Debug, Clone)]
208208
pub struct InnerStorage {
209-
aggregation_number: OptionStorage<AggregationNumber>,
209+
aggregation_number: DefaultStorage<AggregationNumber>,
210210
output_dependent: AutoMapStorage<TaskId, ()>,
211211
output: OptionStorage<OutputValue>,
212212
upper: AutoMapStorage<TaskId, u32>,
@@ -1220,3 +1220,24 @@ where
12201220
None
12211221
}
12221222
}
1223+
1224+
#[cfg(test)]
1225+
mod tests {
1226+
use super::*;
1227+
1228+
#[test]
1229+
fn test_inner_storage_size() {
1230+
// InnerStorage size affects memory usage per task.
1231+
// We track this to catch unexpected bloat from type changes.
1232+
assert_eq!(
1233+
std::mem::size_of::<InnerStorage>(),
1234+
128,
1235+
"InnerStorage size changed - please review if this is intentional"
1236+
);
1237+
assert_eq!(
1238+
std::mem::size_of::<InnerStorageSnapshot>(),
1239+
128,
1240+
"InnerStorageSnapshot size changed - please review if this is intentional"
1241+
);
1242+
}
1243+
}

turbopack/crates/turbo-tasks-backend/src/data_storage.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,120 @@ impl<V> Storage for OptionStorage<V> {
133133
}
134134
}
135135

136+
/// Storage for a single value that uses `Default::default()` to initialize the value "empty".
137+
#[derive(Debug, Clone)]
138+
pub struct DefaultStorage<V> {
139+
value: V,
140+
}
141+
142+
impl<V: Default> Default for DefaultStorage<V> {
143+
fn default() -> Self {
144+
Self {
145+
value: V::default(),
146+
}
147+
}
148+
}
149+
150+
impl<V: Default + PartialEq> Storage for DefaultStorage<V> {
151+
type K = ();
152+
type V = V;
153+
type Iterator<'l>
154+
= std::option::IntoIter<(&'l (), &'l V)>
155+
where
156+
Self: 'l;
157+
158+
fn add(&mut self, _: (), value: V) -> bool {
159+
if self.value == V::default() {
160+
self.value = value;
161+
true
162+
} else {
163+
false
164+
}
165+
}
166+
167+
fn extend(&mut self, items: impl Iterator<Item = (Self::K, Self::V)>) -> bool {
168+
let mut added = true;
169+
for (_, value) in items {
170+
added = self.insert((), value).is_none() && added;
171+
}
172+
added
173+
}
174+
175+
fn insert(&mut self, _: (), value: V) -> Option<V> {
176+
let old = std::mem::replace(&mut self.value, value);
177+
if old == V::default() { None } else { Some(old) }
178+
}
179+
180+
fn remove(&mut self, _: &()) -> Option<V> {
181+
let old = std::mem::take(&mut self.value);
182+
if old == V::default() { None } else { Some(old) }
183+
}
184+
185+
fn contains_key(&self, _: &()) -> bool {
186+
self.value != V::default()
187+
}
188+
189+
fn get(&self, _: &()) -> Option<&V> {
190+
if self.value != V::default() {
191+
Some(&self.value)
192+
} else {
193+
None
194+
}
195+
}
196+
197+
fn get_mut(&mut self, _: &()) -> Option<&mut V> {
198+
if self.value != V::default() {
199+
Some(&mut self.value)
200+
} else {
201+
None
202+
}
203+
}
204+
205+
fn get_mut_or_insert_with(&mut self, _: (), f: impl FnOnce() -> V) -> &mut V {
206+
if self.value == V::default() {
207+
self.value = f();
208+
}
209+
&mut self.value
210+
}
211+
212+
fn shrink_to_fit(&mut self) {
213+
// Nothing to do
214+
}
215+
216+
fn is_empty(&self) -> bool {
217+
self.value == V::default()
218+
}
219+
220+
fn len(&self) -> usize {
221+
if self.value != V::default() { 1 } else { 0 }
222+
}
223+
224+
fn iter(&self) -> Self::Iterator<'_> {
225+
if self.value != V::default() {
226+
Some(value_to_key_value(&self.value)).into_iter()
227+
} else {
228+
None.into_iter()
229+
}
230+
}
231+
232+
fn extract_if<'l, F>(&'l mut self, mut f: F) -> impl Iterator<Item = (Self::K, Self::V)>
233+
where
234+
F: for<'a, 'b> FnMut(&'a Self::K, &'b mut Self::V) -> bool + 'l,
235+
{
236+
if self.value != V::default() && f(&(), &mut self.value) {
237+
let old = std::mem::take(&mut self.value);
238+
return Some(((), old)).into_iter();
239+
}
240+
None.into_iter()
241+
}
242+
243+
fn update(&mut self, _: (), update: impl FnOnce(Option<V>) -> Option<V>) {
244+
let old = std::mem::take(&mut self.value);
245+
let old_opt = if old == V::default() { None } else { Some(old) };
246+
self.value = update(old_opt).unwrap_or_default();
247+
}
248+
}
249+
136250
#[derive(Debug, Clone)]
137251
pub struct AutoMapStorage<K, V> {
138252
map: AutoMap<K, V, BuildHasherDefault<FxHasher>, 1>,

0 commit comments

Comments
 (0)