Skip to content

Commit 2de993f

Browse files
committed
shard jar map
1 parent 87a730f commit 2de993f

File tree

10 files changed

+139
-35
lines changed

10 files changed

+139
-35
lines changed

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ description = "A generic framework for on-demand, incrementalized computation (e
1212
salsa-macro-rules = { version = "0.22.0", path = "components/salsa-macro-rules" }
1313
salsa-macros = { version = "0.22.0", path = "components/salsa-macros", optional = true }
1414

15-
boxcar = "0.2.13"
15+
boxcar = { git = "https://github.com/ibraheemdev/boxcar", rev = "574c893f0a6d8b2cde17dd9933fd9e3d74ea8e0f" }
1616
crossbeam-queue = "0.3.11"
1717
crossbeam-utils = "0.8.21"
18+
dashmap = { version = "6", features = ["raw-api"] }
1819
hashbrown = "0.15"
20+
# The version of hashbrown used by dashmap.
21+
hashbrown_14 = { package = "hashbrown", version = "0.14" }
1922
hashlink = "0.10"
2023
indexmap = "2"
2124
intrusive-collections = "0.9.7"
@@ -51,7 +54,6 @@ salsa-macros = { version = "=0.22.0", path = "components/salsa-macros" }
5154
[dev-dependencies]
5255
# examples
5356
crossbeam-channel = "0.5.14"
54-
dashmap = { version = "6", features = ["raw-api"] }
5557
eyre = "0.6.8"
5658
notify-debouncer-mini = "0.4.1"
5759
ordered-float = "4.2.1"

components/salsa-macro-rules/src/setup_tracked_fn.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,16 @@ macro_rules! setup_tracked_fn {
242242
}
243243
}
244244

245+
fn ingredients_count() -> usize {
246+
$zalsa::macro_if! {
247+
if $needs_interner {
248+
2
249+
} else {
250+
1
251+
}
252+
}
253+
}
254+
245255
fn create_ingredients(
246256
zalsa: &$zalsa::Zalsa,
247257
first_index: $zalsa::IngredientIndex,

src/accumulator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ impl<A: Accumulator> Default for JarImpl<A> {
4343
}
4444

4545
impl<A: Accumulator> Jar for JarImpl<A> {
46+
fn ingredients_count() -> usize {
47+
1
48+
}
49+
4650
fn create_ingredients(
4751
_zalsa: &Zalsa,
4852
first_index: IngredientIndex,

src/ingredient.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,15 @@ pub trait Jar: Any {
3030
IngredientIndices::empty()
3131
}
3232

33+
/// Returns the number of ingredients that `create_ingredients` will return.
34+
fn ingredients_count() -> usize
35+
where
36+
Self: Sized;
37+
3338
/// Create the ingredients given the index of the first one.
3439
/// All subsequent ingredients will be assigned contiguous indices.
40+
///
41+
/// Note that the vector returned must be of length `ingredients_count`.
3542
fn create_ingredients(
3643
zalsa: &Zalsa,
3744
first_index: IngredientIndex,

src/input.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ impl<C: Configuration> Default for JarImpl<C> {
5555
}
5656

5757
impl<C: Configuration> Jar for JarImpl<C> {
58+
fn ingredients_count() -> usize {
59+
1 + C::FIELD_DEBUG_NAMES.len()
60+
}
61+
5862
fn create_ingredients(
5963
_zalsa: &Zalsa,
6064
struct_index: crate::zalsa::IngredientIndex,

src/interned.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ impl<C: Configuration> Default for JarImpl<C> {
201201
}
202202

203203
impl<C: Configuration> Jar for JarImpl<C> {
204+
fn ingredients_count() -> usize {
205+
1
206+
}
207+
204208
fn create_ingredients(
205209
_zalsa: &Zalsa,
206210
first_index: IngredientIndex,

src/memo_ingredient_indices.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{Id, IngredientIndex};
99
/// be viewed as a *set* of [`IngredientIndex`], where each instance of the enum can belong
1010
/// to one, potentially different, index. This is what this type represents: a set of
1111
/// `IngredientIndex`.
12-
#[derive(Clone)]
12+
#[derive(Clone, Default)]
1313
pub struct IngredientIndices {
1414
indices: Box<[IngredientIndex]>,
1515
}

src/sync.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@ pub mod shim {
55
pub use shuttle::sync::*;
66
pub use shuttle::{thread, thread_local};
77

8+
/// A polyfill for `dashmap::DashMap`.
9+
pub struct FxDashMap<K, V>(RwLock<HashTable<K, V>>, crate::hash::FxHasher);
10+
11+
type HashTable<K, V> = hashbrown_14::raw::RawTable<(K, dashmap::SharedValue<V>)>;
12+
13+
impl<K, V> Default for FxDashMap<K, V> {
14+
fn default() -> FxDashMap<K, V> {
15+
FxDashMap(RwLock::default(), crate::hash::FxHasher::default())
16+
}
17+
}
18+
19+
impl<K, V> FxDashMap<K, V> {
20+
pub fn shards(&self) -> &[RwLock<HashTable<K, V>>] {
21+
std::slice::from_ref(&self.0)
22+
}
23+
24+
pub fn determine_shard(&self, _hash: usize) -> usize {
25+
0
26+
}
27+
28+
pub fn hasher(&self) -> &crate::hash::FxHasher {
29+
&self.1
30+
}
31+
32+
pub fn clear(&self) {
33+
self.0.write().clear();
34+
}
35+
}
36+
837
/// A wrapper around shuttle's `Mutex` to mirror parking-lot's API.
938
#[derive(Default, Debug)]
1039
pub struct Mutex<T>(shuttle::sync::Mutex<T>);
@@ -139,6 +168,8 @@ pub mod shim {
139168
pub use std::sync::atomic::*;
140169
}
141170

171+
pub(crate) type FxDashMap<K, V> = dashmap::DashMap<K, V, crate::hash::FxHasher>;
172+
142173
/// A wrapper around parking-lot's `Condvar` to mirror shuttle's API.
143174
pub struct Condvar(parking_lot::Condvar);
144175

src/tracked_struct.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ impl<C: Configuration> Default for JarImpl<C> {
109109
}
110110

111111
impl<C: Configuration> Jar for JarImpl<C> {
112+
fn ingredients_count() -> usize {
113+
1 + C::TRACKED_FIELD_INDICES.len()
114+
}
115+
112116
fn create_ingredients(
113117
_zalsa: &Zalsa,
114118
struct_index: crate::zalsa::IngredientIndex,

src/zalsa.rs

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
use std::any::{Any, TypeId};
2-
use std::collections::hash_map;
2+
use std::hash::BuildHasher;
33
use std::marker::PhantomData;
44
use std::mem;
55
use std::num::NonZeroU32;
66
use std::panic::RefUnwindSafe;
77

8+
use dashmap::SharedValue;
89
use rustc_hash::FxHashMap;
910

1011
use crate::ingredient::{Ingredient, Jar};
1112
use crate::nonce::{Nonce, NonceGenerator};
1213
use crate::runtime::Runtime;
1314
use crate::sync::atomic::{AtomicU64, Ordering};
14-
use crate::sync::{Mutex, RwLock};
15+
use crate::sync::{FxDashMap, RwLock};
1516
use crate::table::memo::MemoTableWithTypes;
1617
use crate::table::Table;
1718
use crate::views::Views;
@@ -146,7 +147,7 @@ pub struct Zalsa {
146147
/// first ingredient index will be. This allows ingredients to store their own indices.
147148
/// This may be worth refactoring in the future because it naturally adds more overhead to
148149
/// adding new kinds of ingredients.
149-
jar_map: Mutex<FxHashMap<TypeId, IngredientIndex>>,
150+
jar_map: FxDashMap<TypeId, IngredientIndex>,
150151

151152
/// A map from the `IngredientIndex` to the `TypeId` of its ID struct.
152153
///
@@ -294,44 +295,81 @@ impl Zalsa {
294295
#[inline]
295296
pub fn add_or_lookup_jar_by_type<J: Jar>(&self) -> IngredientIndex {
296297
let jar_type_id = TypeId::of::<J>();
297-
if let Some(index) = self.jar_map.lock().get(&jar_type_id) {
298-
return *index;
298+
299+
let jar_hash = self.jar_map.hasher().hash_one(jar_type_id);
300+
let shard = self.jar_map.determine_shard(jar_hash as usize);
301+
let jar_map = self.jar_map.shards()[shard].read();
302+
303+
if let Some((_, index)) = jar_map.get(jar_hash, |&(key, _)| key == jar_type_id) {
304+
return *index.get();
299305
};
300-
self.add_or_lookup_jar_by_type_slow::<J>(jar_type_id)
306+
307+
self.add_or_lookup_jar_by_type_slow::<J>(jar_type_id, jar_hash, shard)
301308
}
302309

310+
#[cold]
303311
#[inline(never)]
304-
fn add_or_lookup_jar_by_type_slow<J: Jar>(&self, jar_type_id: TypeId) -> IngredientIndex {
305-
let dependencies = J::create_dependencies(self);
306-
let mut jar_map = self.jar_map.lock();
307-
let index = IngredientIndex::from(self.ingredients_vec.count());
308-
match jar_map.entry(jar_type_id) {
309-
hash_map::Entry::Occupied(entry) => {
310-
// Someone made it earlier than us.
311-
return *entry.get();
312-
}
313-
hash_map::Entry::Vacant(entry) => entry.insert(index),
312+
fn add_or_lookup_jar_by_type_slow<J: Jar>(
313+
&self,
314+
jar_type_id: TypeId,
315+
jar_hash: u64,
316+
shard: usize,
317+
) -> IngredientIndex {
318+
let mut dependencies = J::create_dependencies(self);
319+
320+
let mut jar_map = self.jar_map.shards()[shard].write();
321+
322+
// Someone made it earlier than us.
323+
if let Some((_, index)) = jar_map.get(jar_hash, |&(key, _)| key == jar_type_id) {
324+
return *index.get();
314325
};
315-
let ingredients = J::create_ingredients(self, index, dependencies);
316-
for ingredient in ingredients {
317-
let expected_index = ingredient.ingredient_index();
318326

319-
if ingredient.requires_reset_for_new_revision() {
320-
self.ingredients_requiring_reset.push(expected_index);
321-
}
327+
let mut ingredients = None;
328+
let index = self.ingredients_vec
329+
// Claim the ingredient slots eagerly, which guarantees that the indices will be sequential.
330+
.push_many(J::ingredients_count(), |index| {
331+
let ingredients = ingredients.get_or_insert_with(|| {
332+
// Create the ingredients list with the first index.
333+
let ingredient_index = IngredientIndex::from(index);
334+
let ingredients = J::create_ingredients(self, ingredient_index, mem::take(&mut dependencies));
335+
336+
// Create the jar.
337+
let value = (jar_type_id, SharedValue::new(ingredient_index));
338+
jar_map.insert(jar_hash, value, |(_, value)| {
339+
self.jar_map.hasher().hash_one(value.get())
340+
});
341+
342+
ingredients.into_iter()
343+
});
344+
345+
// Get the next ingredient from the list.
346+
let ingredient = ingredients.next().expect(
347+
"`Jar::ingredients` returned less ingredients than specified by `Jar::ingredients_count`",
348+
);
322349

323-
let actual_index = self.ingredients_vec.push(ingredient);
324-
assert_eq!(
325-
expected_index.as_u32() as usize,
326-
actual_index,
327-
"ingredient `{:?}` was predicted to have index `{:?}` but actually has index `{:?}`",
328-
self.ingredients_vec[actual_index],
329-
expected_index.as_u32(),
330-
actual_index,
331-
);
332-
}
350+
let expected_index = ingredient.ingredient_index();
351+
if ingredient.requires_reset_for_new_revision() {
352+
self.ingredients_requiring_reset.push(expected_index);
353+
}
354+
355+
// Ensure we are assigning the ingredient to the correct index in the vector.
356+
assert_eq!(
357+
expected_index.as_u32() as usize,
358+
index,
359+
"ingredient `{:?}` was predicted to have index `{:?}` but actually has index `{:?}`",
360+
ingredient,
361+
expected_index.as_u32(),
362+
index,
363+
);
364+
365+
ingredient
366+
});
333367

368+
// We need to hold the shard lock until all ingredients have been initialized, to avoid
369+
// returning partially-initialized jars.
334370
drop(jar_map);
371+
372+
let index = IngredientIndex::from(index);
335373
self.ingredient_to_id_struct_type_id_map
336374
.write()
337375
.insert(index, J::id_struct_type_id());

0 commit comments

Comments
 (0)