Skip to content

Commit d787074

Browse files
committed
perf: keep the fetch path generic
1 parent 2fa965c commit d787074

2 files changed

Lines changed: 133 additions & 114 deletions

File tree

src/function/execute.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,28 @@ impl<'db, C: Configuration> QueryStateImpl<'db, C> {
8080
}
8181
}
8282

83+
impl<C: Configuration> IngredientImpl<C> {
84+
/// Executes this query through the shared query lifecycle and restores its typed memo.
85+
pub(super) fn execute<'db>(
86+
&'db self,
87+
db: &'db C::DbView,
88+
claim_guard: ClaimGuard<'db>,
89+
opt_old_memo: Option<&'db Memo<'db, C>>,
90+
memo_ingredient_index: MemoIngredientIndex,
91+
) -> Option<&'db Memo<'db, C>> {
92+
let mut state = QueryStateImpl::new(self, db);
93+
let memo = execute_erased(
94+
&mut state,
95+
claim_guard,
96+
opt_old_memo.map(ErasedMemo::new),
97+
memo_ingredient_index,
98+
C::CYCLE_STRATEGY,
99+
)?;
100+
// SAFETY: `state` and all memos it returns belong to `IngredientImpl<C>`.
101+
Some(unsafe { memo.downcast::<C>() })
102+
}
103+
}
104+
83105
impl<'db, C: Configuration> QueryState<'db> for QueryStateImpl<'db, C> {
84106
fn execute_query(&mut self, zalsa: &'db Zalsa, id: Id) {
85107
self.value = Some(C::execute(self.db, C::id_to_input(zalsa, id)));

src/function/fetch.rs

Lines changed: 111 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use crate::cycle::{CycleRecoveryStrategy, IterationStamp};
2-
use crate::database::RawDatabase;
32
use crate::function::eviction::EvictionPolicy;
4-
use crate::function::execute::{QueryState, QueryStateImpl, execute_erased};
3+
use crate::function::execute::{QueryState, QueryStateImpl};
54
use crate::function::memo::{ErasedMemo, Memo};
6-
use crate::function::sync::{ClaimResult, SyncTable};
5+
use crate::function::sync::ClaimResult;
76
use crate::function::{Configuration, IngredientImpl, Reentrancy};
87
use crate::zalsa::{MemoIngredientIndex, Zalsa};
98
use crate::zalsa_local::{QueryRevisions, ZalsaLocal};
@@ -112,134 +111,132 @@ where
112111
memo_ingredient_index: MemoIngredientIndex,
113112
) -> Option<&'db Memo<'db, C>> {
114113
let database_key_index = self.database_key_index(id);
115-
let mut state = QueryStateImpl::new(self, db);
116-
let memo = fetch_cold_erased(
117-
&mut state,
118-
&self.sync_table,
119-
zalsa,
120-
zalsa_local,
121-
db.into(),
122-
database_key_index,
123-
memo_ingredient_index,
124-
C::CYCLE_STRATEGY,
125-
)?;
126-
// SAFETY: `state` and all memos it returns belong to `IngredientImpl<C>`.
127-
Some(unsafe { memo.downcast::<C>() })
128-
}
129-
}
114+
// Try to claim this query: if someone else has claimed it already, go back and start again.
115+
let claim_guard = match self
116+
.sync_table
117+
.try_claim(zalsa, zalsa_local, id, Reentrancy::Allow)
118+
{
119+
ClaimResult::Claimed(guard) => guard,
120+
ClaimResult::Running(blocked_on) => {
121+
let _ = blocked_on.block_on(zalsa);
122+
return None;
123+
}
124+
ClaimResult::Cycle { .. } => {
125+
return Some(self.fetch_cold_cycle(
126+
db,
127+
zalsa,
128+
zalsa_local,
129+
database_key_index,
130+
memo_ingredient_index,
131+
));
132+
}
133+
};
130134

131-
#[allow(clippy::too_many_arguments)]
132-
fn fetch_cold_erased<'db>(
133-
state: &mut dyn QueryState<'db>,
134-
sync_table: &'db SyncTable,
135-
zalsa: &'db Zalsa,
136-
zalsa_local: &'db ZalsaLocal,
137-
db: RawDatabase<'db>,
138-
database_key_index: DatabaseKeyIndex,
139-
memo_ingredient_index: MemoIngredientIndex,
140-
strategy: CycleRecoveryStrategy,
141-
) -> Option<ErasedMemo<'db>> {
142-
let id = database_key_index.key_index();
135+
// Now that we've claimed the item, check again to see if there's a hot value.
136+
let opt_old_memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index);
143137

144-
// Try to claim this query: if someone else has claimed it already, go back and start again.
145-
let claim_guard = match sync_table.try_claim(zalsa, zalsa_local, id, Reentrancy::Allow) {
146-
ClaimResult::Claimed(guard) => guard,
147-
ClaimResult::Running(blocked_on) => {
148-
let _ = blocked_on.block_on(zalsa);
149-
return None;
150-
}
151-
ClaimResult::Cycle { .. } => {
152-
return Some(fetch_cold_cycle_erased(
153-
state,
154-
zalsa,
155-
zalsa_local,
156-
database_key_index,
157-
memo_ingredient_index,
158-
strategy,
159-
));
138+
if let Some(old_memo) = opt_old_memo {
139+
if old_memo.value.is_some()
140+
&& old_memo
141+
.header
142+
.verify_memo(db.into(), &claim_guard, C::CYCLE_STRATEGY, true)
143+
{
144+
// SAFETY: The memo is present in the memo table, and we verified that it is valid
145+
// for the current revision.
146+
return unsafe { Some(self.extend_memo_lifetime(old_memo)) };
147+
}
160148
}
161-
};
162149

163-
// Now that we've claimed the item, check again to see if there's a hot value.
164-
let opt_old_memo = state.get_memo(zalsa, id, memo_ingredient_index);
150+
self.execute(db, claim_guard, opt_old_memo, memo_ingredient_index)
151+
}
165152

166-
if let Some(old_memo) = opt_old_memo {
167-
if old_memo.value_is_some && old_memo.verify_memo(db, &claim_guard, strategy, true) {
168-
return Some(old_memo);
153+
#[cold]
154+
#[inline(never)]
155+
fn fetch_cold_cycle<'db>(
156+
&'db self,
157+
db: &'db C::DbView,
158+
zalsa: &'db Zalsa,
159+
zalsa_local: &'db ZalsaLocal,
160+
database_key_index: DatabaseKeyIndex,
161+
memo_ingredient_index: MemoIngredientIndex,
162+
) -> &'db Memo<'db, C> {
163+
match C::CYCLE_STRATEGY {
164+
CycleRecoveryStrategy::Panic => fetch_cold_cycle_panic(zalsa_local, database_key_index),
165+
CycleRecoveryStrategy::FallbackImmediate | CycleRecoveryStrategy::Fixpoint => {
166+
let mut state = QueryStateImpl::new(self, db);
167+
let memo = fetch_cold_cycle_recoverable_erased(
168+
&mut state,
169+
zalsa,
170+
database_key_index,
171+
memo_ingredient_index,
172+
);
173+
// SAFETY: `state` and all memos it returns belong to `IngredientImpl<C>`.
174+
unsafe { memo.downcast::<C>() }
175+
}
169176
}
170177
}
178+
}
171179

172-
execute_erased(
173-
state,
174-
claim_guard,
175-
opt_old_memo,
176-
memo_ingredient_index,
177-
strategy,
178-
)
180+
#[cold]
181+
fn fetch_cold_cycle_panic(zalsa_local: &ZalsaLocal, database_key_index: DatabaseKeyIndex) -> ! {
182+
// SAFETY: We do not access the query stack reentrantly.
183+
unsafe {
184+
zalsa_local.with_query_stack_unchecked(|stack| {
185+
panic!(
186+
"dependency graph cycle when querying {database_key_index:#?}, \
187+
set cycle_fn/cycle_initial to fixpoint iterate.\n\
188+
Query stack:\n{stack:#?}",
189+
);
190+
})
191+
}
179192
}
180193

181194
#[cold]
182-
fn fetch_cold_cycle_erased<'db>(
195+
fn fetch_cold_cycle_recoverable_erased<'db>(
183196
state: &mut dyn QueryState<'db>,
184197
zalsa: &'db Zalsa,
185-
zalsa_local: &'db ZalsaLocal,
186198
database_key_index: DatabaseKeyIndex,
187199
memo_ingredient_index: MemoIngredientIndex,
188-
strategy: CycleRecoveryStrategy,
189200
) -> ErasedMemo<'db> {
190201
let id = database_key_index.key_index();
191202

192-
match strategy {
193-
// SAFETY: We do not access the query stack reentrantly.
194-
CycleRecoveryStrategy::Panic => unsafe {
195-
zalsa_local.with_query_stack_unchecked(|stack| {
196-
panic!(
197-
"dependency graph cycle when querying {database_key_index:#?}, \
198-
set cycle_fn/cycle_initial to fixpoint iterate.\n\
199-
Query stack:\n{stack:#?}",
200-
);
201-
})
202-
},
203-
CycleRecoveryStrategy::Fixpoint | CycleRecoveryStrategy::FallbackImmediate => {
204-
let cancellation_count = zalsa.runtime().cancellation_count();
205-
// Don't validate provisional memos here: an existing value should be reused.
206-
let current_memo = state
207-
.get_memo(zalsa, id, memo_ingredient_index)
208-
.filter(|memo| {
209-
memo.verified_at.load() == zalsa.current_revision()
210-
&& memo.value_is_some
211-
&& memo.revisions.iteration().cancellation_count() == cancellation_count
212-
});
213-
214-
// Ideally, any current provisional value could be reused. Reusing a value that was
215-
// not a cycle head in the last iteration would require inserting itself as a head,
216-
// which in turn requires cloning the value or making the cycle-head list concurrent.
217-
if let Some(memo) = current_memo
218-
.filter(|memo| memo.revisions.cycle_heads().contains(&database_key_index))
219-
{
220-
memo.revisions
221-
.cycle_heads()
222-
.remove_all_except(database_key_index);
223-
224-
crate::tracing::debug!(
225-
"hit cycle at {database_key_index:#?}, \
226-
returning last provisional value: {:#?}",
227-
memo.revisions
228-
);
229-
return memo;
230-
}
231-
232-
crate::tracing::debug!(
233-
"hit cycle at {database_key_index:#?}, \
234-
inserting and returning fixpoint initial value"
235-
);
236-
237-
let iteration = current_memo
238-
.map(|memo| memo.revisions.iteration())
239-
.unwrap_or_else(|| IterationStamp::initial(cancellation_count));
240-
let revisions = QueryRevisions::fixpoint_initial(database_key_index, iteration);
241-
state.use_fallback(zalsa, id);
242-
state.insert_memo(zalsa, id, revisions, memo_ingredient_index)
243-
}
203+
let cancellation_count = zalsa.runtime().cancellation_count();
204+
// Don't validate provisional memos here: an existing value should be reused.
205+
let current_memo = state
206+
.get_memo(zalsa, id, memo_ingredient_index)
207+
.filter(|memo| {
208+
memo.verified_at.load() == zalsa.current_revision()
209+
&& memo.value_is_some
210+
&& memo.revisions.iteration().cancellation_count() == cancellation_count
211+
});
212+
213+
// Ideally, any current provisional value could be reused. Reusing a value that was not a
214+
// cycle head in the last iteration would require inserting itself as a head, which in turn
215+
// requires cloning the value or making the cycle-head list concurrent.
216+
if let Some(memo) =
217+
current_memo.filter(|memo| memo.revisions.cycle_heads().contains(&database_key_index))
218+
{
219+
memo.revisions
220+
.cycle_heads()
221+
.remove_all_except(database_key_index);
222+
223+
crate::tracing::debug!(
224+
"hit cycle at {database_key_index:#?}, \
225+
returning last provisional value: {:#?}",
226+
memo.revisions
227+
);
228+
return memo;
244229
}
230+
231+
crate::tracing::debug!(
232+
"hit cycle at {database_key_index:#?}, \
233+
inserting and returning fixpoint initial value"
234+
);
235+
236+
let iteration = current_memo
237+
.map(|memo| memo.revisions.iteration())
238+
.unwrap_or_else(|| IterationStamp::initial(cancellation_count));
239+
let revisions = QueryRevisions::fixpoint_initial(database_key_index, iteration);
240+
state.use_fallback(zalsa, id);
241+
state.insert_memo(zalsa, id, revisions, memo_ingredient_index)
245242
}

0 commit comments

Comments
 (0)