Skip to content

Commit 8628352

Browse files
committed
Auto merge of #16505 - Veykril:unit-salsa, r=Veykril
Optimize input queries that take no arguments There is no point in having a hashmap and extra lock for this, it is always only a single value. This might speed up some things by a tiny bit for our crate graph query.
2 parents 2d2ddd3 + 97cd680 commit 8628352

File tree

3 files changed

+181
-34
lines changed

3 files changed

+181
-34
lines changed

crates/salsa/salsa-macros/src/query_group.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ pub(crate) fn query_group(args: TokenStream, input: TokenStream) -> TokenStream
371371
QueryStorage::Dependencies => {
372372
quote!(salsa::plumbing::DependencyStorage<Self>)
373373
}
374+
QueryStorage::Input if query.keys.is_empty() => {
375+
quote!(salsa::plumbing::UnitInputStorage<Self>)
376+
}
374377
QueryStorage::Input => quote!(salsa::plumbing::InputStorage<Self>),
375378
QueryStorage::Interned => quote!(salsa::plumbing::InternedStorage<Self>),
376379
QueryStorage::InternedLookup { intern_query_type } => {

crates/salsa/src/input.rs

Lines changed: 177 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::{DatabaseKeyIndex, QueryDb};
1515
use indexmap::map::Entry;
1616
use parking_lot::RwLock;
1717
use std::convert::TryFrom;
18+
use std::iter;
1819
use tracing::debug;
1920

2021
/// Input queries store the result plus a list of the other queries
@@ -25,15 +26,12 @@ where
2526
Q: Query,
2627
{
2728
group_index: u16,
28-
slots: RwLock<FxIndexMap<Q::Key, Slot<Q>>>,
29+
slots: RwLock<FxIndexMap<Q::Key, Slot<Q::Value>>>,
2930
}
3031

31-
struct Slot<Q>
32-
where
33-
Q: Query,
34-
{
32+
struct Slot<V> {
3533
database_key_index: DatabaseKeyIndex,
36-
stamped_value: RwLock<StampedValue<Q::Value>>,
34+
stamped_value: RwLock<StampedValue<V>>,
3735
}
3836

3937
impl<Q> std::panic::RefUnwindSafe for InputStorage<Q>
@@ -78,7 +76,14 @@ where
7876
debug_assert!(revision < db.salsa_runtime().current_revision());
7977
let slots = &self.slots.read();
8078
let slot = slots.get_index(input.key_index as usize).unwrap().1;
81-
slot.maybe_changed_after(db, revision)
79+
80+
debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
81+
82+
let changed_at = slot.stamped_value.read().changed_at;
83+
84+
debug!("maybe_changed_after: changed_at = {:?}", changed_at);
85+
86+
changed_at > revision
8287
}
8388

8489
fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, key: &Q::Key) -> Q::Value {
@@ -121,21 +126,6 @@ where
121126
}
122127
}
123128

124-
impl<Q> Slot<Q>
125-
where
126-
Q: Query,
127-
{
128-
fn maybe_changed_after(&self, _db: &<Q as QueryDb<'_>>::DynDb, revision: Revision) -> bool {
129-
debug!("maybe_changed_after(slot={:?}, revision={:?})", self, revision,);
130-
131-
let changed_at = self.stamped_value.read().changed_at;
132-
133-
debug!("maybe_changed_after: changed_at = {:?}", changed_at);
134-
135-
changed_at > revision
136-
}
137-
}
138-
139129
impl<Q> QueryStorageMassOps for InputStorage<Q>
140130
where
141131
Q: Query,
@@ -202,6 +192,167 @@ where
202192
}
203193
}
204194

195+
/// Same as `InputStorage`, but optimized for queries that take no inputs.
196+
pub struct UnitInputStorage<Q>
197+
where
198+
Q: Query<Key = ()>,
199+
{
200+
group_index: u16,
201+
slot: UnitSlot<Q::Value>,
202+
}
203+
204+
struct UnitSlot<V> {
205+
database_key_index: DatabaseKeyIndex,
206+
stamped_value: RwLock<Option<StampedValue<V>>>,
207+
}
208+
209+
impl<Q> std::panic::RefUnwindSafe for UnitInputStorage<Q>
210+
where
211+
Q: Query<Key = ()>,
212+
Q::Key: std::panic::RefUnwindSafe,
213+
Q::Value: std::panic::RefUnwindSafe,
214+
{
215+
}
216+
217+
impl<Q> QueryStorageOps<Q> for UnitInputStorage<Q>
218+
where
219+
Q: Query<Key = ()>,
220+
{
221+
const CYCLE_STRATEGY: crate::plumbing::CycleRecoveryStrategy = CycleRecoveryStrategy::Panic;
222+
223+
fn new(group_index: u16) -> Self {
224+
let database_key_index =
225+
DatabaseKeyIndex { group_index, query_index: Q::QUERY_INDEX, key_index: 0 };
226+
UnitInputStorage {
227+
group_index,
228+
slot: UnitSlot { database_key_index, stamped_value: RwLock::new(None) },
229+
}
230+
}
231+
232+
fn fmt_index(
233+
&self,
234+
_db: &<Q as QueryDb<'_>>::DynDb,
235+
index: DatabaseKeyIndex,
236+
fmt: &mut std::fmt::Formatter<'_>,
237+
) -> std::fmt::Result {
238+
assert_eq!(index.group_index, self.group_index);
239+
assert_eq!(index.query_index, Q::QUERY_INDEX);
240+
write!(fmt, "{}", Q::QUERY_NAME)
241+
}
242+
243+
fn maybe_changed_after(
244+
&self,
245+
db: &<Q as QueryDb<'_>>::DynDb,
246+
input: DatabaseKeyIndex,
247+
revision: Revision,
248+
) -> bool {
249+
assert_eq!(input.group_index, self.group_index);
250+
assert_eq!(input.query_index, Q::QUERY_INDEX);
251+
debug_assert!(revision < db.salsa_runtime().current_revision());
252+
253+
debug!("maybe_changed_after(slot={:?}, revision={:?})", Q::default(), revision,);
254+
255+
let changed_at = self.slot.stamped_value.read().as_ref().unwrap().changed_at;
256+
257+
debug!("maybe_changed_after: changed_at = {:?}", changed_at);
258+
259+
changed_at > revision
260+
}
261+
262+
fn fetch(&self, db: &<Q as QueryDb<'_>>::DynDb, &(): &Q::Key) -> Q::Value {
263+
db.unwind_if_cancelled();
264+
265+
let StampedValue { value, durability, changed_at } = self
266+
.slot
267+
.stamped_value
268+
.read()
269+
.clone()
270+
.unwrap_or_else(|| panic!("no value set for {:?}", Q::default()));
271+
272+
db.salsa_runtime().report_query_read_and_unwind_if_cycle_resulted(
273+
self.slot.database_key_index,
274+
durability,
275+
changed_at,
276+
);
277+
278+
value
279+
}
280+
281+
fn durability(&self, _db: &<Q as QueryDb<'_>>::DynDb, &(): &Q::Key) -> Durability {
282+
match &*self.slot.stamped_value.read() {
283+
Some(stamped_value) => stamped_value.durability,
284+
None => panic!("no value set for {:?}", Q::default(),),
285+
}
286+
}
287+
288+
fn entries<C>(&self, _db: &<Q as QueryDb<'_>>::DynDb) -> C
289+
where
290+
C: std::iter::FromIterator<TableEntry<Q::Key, Q::Value>>,
291+
{
292+
iter::once(TableEntry::new(
293+
(),
294+
self.slot.stamped_value.read().as_ref().map(|it| it.value.clone()),
295+
))
296+
.collect()
297+
}
298+
}
299+
300+
impl<Q> QueryStorageMassOps for UnitInputStorage<Q>
301+
where
302+
Q: Query<Key = ()>,
303+
{
304+
fn purge(&self) {
305+
*self.slot.stamped_value.write() = Default::default();
306+
}
307+
}
308+
309+
impl<Q> InputQueryStorageOps<Q> for UnitInputStorage<Q>
310+
where
311+
Q: Query<Key = ()>,
312+
{
313+
fn set(&self, runtime: &mut Runtime, (): &Q::Key, value: Q::Value, durability: Durability) {
314+
tracing::debug!("{:?} = {:?} ({:?})", Q::default(), value, durability);
315+
316+
// The value is changing, so we need a new revision (*). We also
317+
// need to update the 'last changed' revision by invoking
318+
// `guard.mark_durability_as_changed`.
319+
//
320+
// CAREFUL: This will block until the global revision lock can
321+
// be acquired. If there are still queries executing, they may
322+
// need to read from this input. Therefore, we wait to acquire
323+
// the lock on `map` until we also hold the global query write
324+
// lock.
325+
//
326+
// (*) Technically, since you can't presently access an input
327+
// for a non-existent key, and you can't enumerate the set of
328+
// keys, we only need a new revision if the key used to
329+
// exist. But we may add such methods in the future and this
330+
// case doesn't generally seem worth optimizing for.
331+
runtime.with_incremented_revision(|next_revision| {
332+
let mut stamped_value_slot = self.slot.stamped_value.write();
333+
334+
// Do this *after* we acquire the lock, so that we are not
335+
// racing with somebody else to modify this same cell.
336+
// (Otherwise, someone else might write a *newer* revision
337+
// into the same cell while we block on the lock.)
338+
let stamped_value = StampedValue { value, durability, changed_at: next_revision };
339+
340+
match &mut *stamped_value_slot {
341+
Some(slot_stamped_value) => {
342+
let old_durability = slot_stamped_value.durability;
343+
*slot_stamped_value = stamped_value;
344+
Some(old_durability)
345+
}
346+
347+
stamped_value_slot @ None => {
348+
*stamped_value_slot = Some(stamped_value);
349+
None
350+
}
351+
}
352+
});
353+
}
354+
}
355+
205356
/// Check that `Slot<Q, MP>: Send + Sync` as long as
206357
/// `DB::DatabaseData: Send + Sync`, which in turn implies that
207358
/// `Q::Key: Send + Sync`, `Q::Value: Send + Sync`.
@@ -213,7 +364,8 @@ where
213364
Q::Value: Send + Sync,
214365
{
215366
fn is_send_sync<T: Send + Sync>() {}
216-
is_send_sync::<Slot<Q>>();
367+
is_send_sync::<Slot<Q::Value>>();
368+
is_send_sync::<UnitSlot<Q::Value>>();
217369
}
218370

219371
/// Check that `Slot<Q, MP>: 'static` as long as
@@ -227,14 +379,6 @@ where
227379
Q::Value: 'static,
228380
{
229381
fn is_static<T: 'static>() {}
230-
is_static::<Slot<Q>>();
231-
}
232-
233-
impl<Q> std::fmt::Debug for Slot<Q>
234-
where
235-
Q: Query,
236-
{
237-
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238-
write!(fmt, "{:?}", Q::default())
239-
}
382+
is_static::<Slot<Q::Value>>();
383+
is_static::<UnitSlot<Q::Value>>();
240384
}

crates/salsa/src/plumbing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use triomphe::Arc;
1515

1616
pub use crate::derived::DependencyStorage;
1717
pub use crate::derived::MemoizedStorage;
18-
pub use crate::input::InputStorage;
18+
pub use crate::input::{InputStorage, UnitInputStorage};
1919
pub use crate::interned::InternedStorage;
2020
pub use crate::interned::LookupInternedStorage;
2121
pub use crate::{revision::Revision, DatabaseKeyIndex, QueryDb, Runtime};

0 commit comments

Comments
 (0)