Skip to content

Commit 5c1eeeb

Browse files
committed
optimize allocation fast-path
1 parent 0ca4f4f commit 5c1eeeb

File tree

5 files changed

+59
-15
lines changed

5 files changed

+59
-15
lines changed

src/input.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,16 @@ impl<C: Configuration> IngredientImpl<C> {
111111
durabilities: C::Durabilities,
112112
) -> C::Struct {
113113
let id = self.singleton.with_scope(|| {
114-
zalsa_local.allocate(zalsa, self.ingredient_index, |_| Value::<C> {
114+
let (id, _) = zalsa_local.allocate(zalsa, self.ingredient_index, |_| Value::<C> {
115115
fields,
116116
revisions,
117117
durabilities,
118118
// SAFETY: We only ever access the memos of a value that we allocated through
119119
// our `MemoTableTypes`.
120120
memos: unsafe { MemoTable::new(self.memo_table_types()) },
121-
})
121+
});
122+
123+
id
122124
});
123125

124126
FromIdWithDb::from_id(id, zalsa)

src/interned.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ where
583583
.unwrap_or((Durability::MAX, Revision::max()));
584584

585585
// Allocate the value slot.
586-
let id = zalsa_local.allocate(zalsa, self.ingredient_index, |id| Value::<C> {
586+
let (id, value) = zalsa_local.allocate(zalsa, self.ingredient_index, |id| Value::<C> {
587587
shard: shard_index as u16,
588588
link: LinkedListLink::new(),
589589
// SAFETY: We only ever access the memos of a value that we allocated through
@@ -598,7 +598,6 @@ where
598598
}),
599599
});
600600

601-
let value = zalsa.table().get::<Value<C>>(id);
602601
// SAFETY: We hold the lock for the shard containing the value.
603602
let value_shared = unsafe { &mut *value.shared.get() };
604603

src/table.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,17 @@ impl Table {
207207
/// If `page` is out of bounds or the type `T` is incorrect.
208208
#[inline]
209209
pub(crate) fn page<T: Slot>(&self, page: PageIndex) -> PageView<'_, T> {
210-
self.pages[page.0].assert_type::<T>()
210+
#[cold]
211+
#[inline(never)]
212+
fn assert_failed(index: usize) -> ! {
213+
panic!("index {index} is uninitialized");
214+
}
215+
216+
if let Some(page) = self.pages.get(page.0) {
217+
return page.assert_type();
218+
}
219+
220+
assert_failed(page.0)
211221
}
212222

213223
/// Allocate a new page for the given ingredient and with slots of type `T`
@@ -286,6 +296,8 @@ impl Table {
286296
.flat_map(|view| view.data())
287297
}
288298

299+
#[cold]
300+
#[inline(never)]
289301
pub(crate) fn fetch_or_push_page<T: Slot>(
290302
&self,
291303
ingredient: IngredientIndex,
@@ -299,6 +311,7 @@ impl Table {
299311
{
300312
return page;
301313
}
314+
302315
self.push_page::<T>(ingredient, memo_types())
303316
}
304317

@@ -311,22 +324,23 @@ impl Table {
311324
}
312325
}
313326

314-
impl<'p, T: Slot> PageView<'p, T> {
327+
impl<'db, T: Slot> PageView<'db, T> {
315328
#[inline]
316-
fn page_data(&self) -> &'p [PageDataEntry<T>] {
329+
fn page_data(&self) -> &'db [PageDataEntry<T>] {
317330
let len = self.0.allocated.load(Ordering::Acquire);
318331
// SAFETY: `len` is the initialized length of the page
319332
unsafe { slice::from_raw_parts(self.0.data.cast::<PageDataEntry<T>>().as_ptr(), len) }
320333
}
321334

322335
#[inline]
323-
fn data(&self) -> &'p [T] {
336+
fn data(&self) -> &'db [T] {
324337
let len = self.0.allocated.load(Ordering::Acquire);
325338
// SAFETY: `len` is the initialized length of the page
326339
unsafe { slice::from_raw_parts(self.0.data.cast::<T>().as_ptr(), len) }
327340
}
328341

329-
pub(crate) fn allocate<V>(&self, page: PageIndex, value: V) -> Result<Id, V>
342+
#[inline]
343+
pub(crate) fn allocate<V>(&self, page: PageIndex, value: V) -> Result<(Id, &'db T), V>
330344
where
331345
V: FnOnce(Id) -> T,
332346
{
@@ -347,11 +361,14 @@ impl<'p, T: Slot> PageView<'p, T> {
347361
// interior
348362
unsafe { (*entry.get()).write(value(id)) };
349363

364+
// SAFETY: We just initialized the value above.
365+
let value = unsafe { (*entry.get()).assume_init_ref() };
366+
350367
// Update the length (this must be done after initialization as otherwise an uninitialized
351368
// read could occur!)
352369
self.0.allocated.store(index + 1, Ordering::Release);
353370

354-
Ok(id)
371+
Ok((id, value))
355372
}
356373
}
357374

src/tracked_struct.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,9 @@ where
477477
return id;
478478
}
479479

480-
zalsa_local.allocate::<Value<C>>(zalsa, self.ingredient_index, value)
480+
let (id, _) = zalsa_local.allocate::<Value<C>>(zalsa, self.ingredient_index, value);
481+
482+
id
481483
}
482484

483485
/// Get mutable access to the data for `id` -- this holds a write lock for the duration

src/zalsa_local.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,36 @@ impl ZalsaLocal {
5353
/// Allocate a new id in `table` for the given ingredient
5454
/// storing `value`. Remembers the most recent page from this
5555
/// thread and attempts to reuse it.
56-
pub(crate) fn allocate<T: Slot>(
56+
pub(crate) fn allocate<'db, T: Slot>(
5757
&self,
58-
zalsa: &Zalsa,
58+
zalsa: &'db Zalsa,
5959
ingredient: IngredientIndex,
6060
mut value: impl FnOnce(Id) -> T,
61-
) -> Id {
61+
) -> (Id, &'db T) {
62+
// SAFETY: `ZalsaLocal` is `!Sync`, and we never expose a reference to this field,
63+
// so we have exclusive access.
64+
let most_recent_pages = unsafe { &mut *self.most_recent_pages.get() };
65+
66+
// Fast-path, we already have an unfilled page available.
67+
if let Some(&page) = most_recent_pages.get(&ingredient) {
68+
let page_ref = zalsa.table().page::<T>(page);
69+
match page_ref.allocate(page, value) {
70+
Ok((id, value)) => return (id, value),
71+
Err(v) => value = v,
72+
}
73+
}
74+
75+
self.allocate_cold(zalsa, ingredient, value)
76+
}
77+
78+
#[cold]
79+
#[inline(never)]
80+
pub(crate) fn allocate_cold<'db, T: Slot>(
81+
&self,
82+
zalsa: &'db Zalsa,
83+
ingredient: IngredientIndex,
84+
mut value: impl FnOnce(Id) -> T,
85+
) -> (Id, &'db T) {
6286
let memo_types = || {
6387
zalsa
6488
.lookup_ingredient(ingredient)
@@ -82,7 +106,7 @@ impl ZalsaLocal {
82106
let page_ref = zalsa.table().page::<T>(page);
83107
match page_ref.allocate(page, value) {
84108
// If successful, return
85-
Ok(id) => return id,
109+
Ok((id, value)) => return (id, value),
86110

87111
// Otherwise, create a new page and try again
88112
// Note that we could try fetching a page again, but as we just filled one up

0 commit comments

Comments
 (0)