Skip to content

Commit ec23b64

Browse files
Don't store the fields in the interned map
Instead, store only an `Id`, to save memory.
1 parent ad4ea3f commit ec23b64

File tree

3 files changed

+35
-10
lines changed

3 files changed

+35
-10
lines changed

src/id.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl Id {
4242
/// salsa computations.
4343
#[doc(hidden)]
4444
#[track_caller]
45+
#[inline]
4546
pub const unsafe fn from_u32(v: u32) -> Self {
4647
debug_assert!(v < Self::MAX_U32);
4748
Id {
@@ -50,6 +51,7 @@ impl Id {
5051
}
5152
}
5253

54+
#[inline]
5355
pub const fn as_u32(self) -> u32 {
5456
self.value.get() - 1
5557
}

src/interned.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(clippy::undocumented_unsafe_blocks)] // TODO(#697) document safety
22

33
use std::any::TypeId;
4+
use std::cell::Cell;
45
use std::fmt;
56
use std::hash::{BuildHasher, Hash, Hasher};
67
use std::marker::PhantomData;
@@ -60,10 +61,14 @@ pub struct IngredientImpl<C: Configuration> {
6061

6162
/// Maps from data to the existing interned id for that data.
6263
///
64+
/// This doesn't hold the fields themselves to save memory, instead it points to the slot ID.
65+
///
6366
/// Deadlock requirement: We access `value_map` while holding lock on `key_map`, but not vice versa.
64-
key_map: FxDashMap<C::Fields<'static>, Id>,
67+
key_map: FxDashMap<Id, ()>,
6568

6669
memo_table_types: Arc<MemoTableTypes>,
70+
71+
_marker: PhantomData<fn() -> C>,
6772
}
6873

6974
/// Struct storing the interned fields.
@@ -135,6 +140,7 @@ where
135140
ingredient_index,
136141
key_map: Default::default(),
137142
memo_table_types: Arc::new(MemoTableTypes::default()),
143+
_marker: PhantomData,
138144
}
139145
}
140146

@@ -193,24 +199,32 @@ where
193199
{
194200
let (zalsa, zalsa_local) = db.zalsas();
195201
let current_revision = zalsa.current_revision();
202+
let table = zalsa.table();
196203

197204
// Optimization to only get read lock on the map if the data has already been interned.
198205
let data_hash = self.key_map.hasher().hash_one(&key);
199206
let shard = &self.key_map.shards()[self.key_map.determine_shard(data_hash as _)];
200-
let eq = |(data, _): &_| {
207+
let found_value = Cell::new(None);
208+
let eq = |(id, _): &_| {
209+
let data = table.get::<Value<C>>(*id);
210+
found_value.set(Some(data));
201211
// SAFETY: it's safe to go from Data<'static> to Data<'db>
202212
// shrink lifetime here to use a single lifetime in Lookup::eq(&StructKey<'db>, &C::Data<'db>)
203-
let data: &C::Fields<'db> = unsafe { std::mem::transmute(data) };
213+
let data = unsafe {
214+
std::mem::transmute::<&C::Fields<'static>, &C::Fields<'db>>(&data.fields)
215+
};
204216
HashEqLike::eq(data, &key)
205217
};
206218

207219
{
208220
let lock = shard.read();
209221
if let Some(bucket) = lock.find(data_hash, eq) {
210222
// SAFETY: Read lock on map is held during this block
211-
let id = unsafe { *bucket.as_ref().1.get() };
223+
let id = unsafe { bucket.as_ref().0 };
212224

213-
let value = zalsa.table().get::<Value<C>>(id);
225+
let value = found_value
226+
.get()
227+
.expect("found the interned, so `found_value` should be set");
214228

215229
// Sync the value's revision.
216230
if value.last_interned_at.load() < current_revision {
@@ -243,12 +257,16 @@ where
243257
}
244258

245259
let mut lock = shard.write();
246-
match lock.find_or_find_insert_slot(data_hash, eq, |(element, _)| {
247-
self.key_map.hasher().hash_one(element)
260+
match lock.find_or_find_insert_slot(data_hash, eq, |(id, _)| {
261+
// This closure is only called if the table is resized. So while it's expensive to lookup all values,
262+
// it will only happen rarely.
263+
self.key_map
264+
.hasher()
265+
.hash_one(&table.get::<Value<C>>(*id).fields)
248266
}) {
249267
// Data has been interned by a racing call, use that ID instead
250268
Ok(slot) => {
251-
let id = unsafe { *slot.as_ref().1.get() };
269+
let id = unsafe { slot.as_ref().0 };
252270
let value = zalsa.table().get::<Value<C>>(id);
253271

254272
// Sync the value's revision.
@@ -302,8 +320,7 @@ where
302320

303321
let value = zalsa.table().get::<Value<C>>(id);
304322

305-
let slot_value = (value.fields.clone(), SharedValue::new(id));
306-
unsafe { lock.insert_in_slot(data_hash, slot, slot_value) };
323+
unsafe { lock.insert_in_slot(data_hash, slot, (id, SharedValue::new(()))) };
307324

308325
debug_assert_eq!(
309326
data_hash,

src/table.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ impl PageIndex {
164164
struct SlotIndex(usize);
165165

166166
impl SlotIndex {
167+
#[inline]
167168
fn new(idx: usize) -> Self {
168169
debug_assert!(idx < PAGE_LEN);
169170
Self(idx)
@@ -192,6 +193,7 @@ impl Table {
192193
/// # Panics
193194
///
194195
/// If `id` is out of bounds or the does not have the type `T`.
196+
#[inline]
195197
pub(crate) fn get<T: Slot>(&self, id: Id) -> &T {
196198
let (page, slot) = split_id(id);
197199
let page_ref = self.page::<T>(page);
@@ -218,6 +220,7 @@ impl Table {
218220
/// # Panics
219221
///
220222
/// If `page` is out of bounds or the type `T` is incorrect.
223+
#[inline]
221224
pub(crate) fn page<T: Slot>(&self, page: PageIndex) -> PageView<T> {
222225
self.pages[page.0].assert_type::<T>()
223226
}
@@ -317,6 +320,7 @@ impl<'p, T: Slot> PageView<'p, T> {
317320
unsafe { slice::from_raw_parts(self.0.data.cast::<PageDataEntry<T>>().as_ptr(), len) }
318321
}
319322

323+
#[inline]
320324
fn data(&self) -> &'p [T] {
321325
let len = self.0.allocated.load(Ordering::Acquire);
322326
// SAFETY: `len` is the initialized length of the page
@@ -385,6 +389,7 @@ impl Page {
385389
}
386390
}
387391

392+
#[inline]
388393
fn assert_type<T: Slot>(&self) -> PageView<T> {
389394
assert_eq!(
390395
self.slot_type_id,
@@ -420,6 +425,7 @@ fn make_id(page: PageIndex, slot: SlotIndex) -> Id {
420425
unsafe { Id::from_u32((page << PAGE_LEN_BITS) | slot) }
421426
}
422427

428+
#[inline]
423429
fn split_id(id: Id) -> (PageIndex, SlotIndex) {
424430
let id = id.as_u32() as usize;
425431
let slot = id & PAGE_LEN_MASK;

0 commit comments

Comments
 (0)