Skip to content

Commit a0aa1c1

Browse files
Cache trait solving across queries in the same revision
Caching trait solving can do a lot to speed. Unfortunately it also consume a huge amount of memory. Therefore, as part of the migration to the new solver Jack Huey disabled caching of trait solving (he made the query transparent). The PR proposes a middle ground: do cache trait solving, but only for the same revision. This allows us to be safe because during a revision the inputs cannot change. The result is hopefully much better performance to features that tend to do a bulk of trait solving, and also repeat the same query (e.g. inference then IDE features). There is another limitation: results are only cached in the same thread, to remove the need for synchronization which will be expensive. More measurements are required to check whether it's better to use a synchronized global cache, or maybe stay with a thread-local cache but batch multiple feature requests (highlighting, inlay hints etc.) of the same file to the same thread. Alongside the actual cache we store the revision, because we need to verify it (we can't eagerly clear caches when incrementing the revision), and also the address of the db to prevent multiple dbs from interleaving (this is mostly relevant in tests, although injected highlighting also uses a new db, therefore maybe it's better to move it to a separate thread). This "games" analysis-stats to both be way faster and use way more memory; the former is because analysis-stats doesn't increment revisions, therefore all queries share the cache and hit ratio is way too good, the latter is because analysis-stats doesn't increment revisions and therefore the cache isn't cleared. Both are not representative of a typical IDE scenario.
1 parent cf358c0 commit a0aa1c1

File tree

7 files changed

+140
-66
lines changed

7 files changed

+140
-66
lines changed

src/tools/rust-analyzer/crates/base-db/src/lib.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ pub use salsa_macros;
77
mod change;
88
mod input;
99

10-
use std::{cell::RefCell, hash::BuildHasherDefault, panic, sync::Once};
10+
use std::{
11+
cell::RefCell,
12+
hash::BuildHasherDefault,
13+
panic,
14+
sync::{Once, atomic::AtomicUsize},
15+
};
1116

1217
pub use crate::{
1318
change::FileChange,
@@ -328,6 +333,27 @@ pub trait SourceDatabase: salsa::Database {
328333

329334
#[doc(hidden)]
330335
fn crates_map(&self) -> Arc<CratesMap>;
336+
337+
fn nonce_and_revision(&self) -> (Nonce, salsa::Revision);
338+
}
339+
340+
static NEXT_NONCE: AtomicUsize = AtomicUsize::new(0);
341+
342+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
343+
pub struct Nonce(usize);
344+
345+
impl Default for Nonce {
346+
#[inline]
347+
fn default() -> Self {
348+
Nonce::new()
349+
}
350+
}
351+
352+
impl Nonce {
353+
#[inline]
354+
pub fn new() -> Nonce {
355+
Nonce(NEXT_NONCE.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
356+
}
331357
}
332358

333359
/// Crate related data shared by the whole workspace.

src/tools/rust-analyzer/crates/hir-def/src/test_db.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use std::{fmt, panic, sync::Mutex};
44

55
use base_db::{
6-
Crate, CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, RootQueryDb,
6+
Crate, CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, RootQueryDb,
77
SourceDatabase, SourceRoot, SourceRootId, SourceRootInput,
88
};
99
use hir_expand::{InFile, files::FilePosition};
@@ -20,12 +20,12 @@ use crate::{
2020
};
2121

2222
#[salsa_macros::db]
23-
#[derive(Clone)]
2423
pub(crate) struct TestDB {
2524
storage: salsa::Storage<Self>,
2625
files: Arc<base_db::Files>,
2726
crates_map: Arc<CratesMap>,
2827
events: Arc<Mutex<Option<Vec<salsa::Event>>>>,
28+
nonce: Nonce,
2929
}
3030

3131
impl Default for TestDB {
@@ -44,6 +44,7 @@ impl Default for TestDB {
4444
events,
4545
files: Default::default(),
4646
crates_map: Default::default(),
47+
nonce: Nonce::new(),
4748
};
4849
this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH);
4950
// This needs to be here otherwise `CrateGraphBuilder` panics.
@@ -53,6 +54,18 @@ impl Default for TestDB {
5354
}
5455
}
5556

57+
impl Clone for TestDB {
58+
fn clone(&self) -> Self {
59+
Self {
60+
storage: self.storage.clone(),
61+
files: self.files.clone(),
62+
crates_map: self.crates_map.clone(),
63+
events: self.events.clone(),
64+
nonce: Nonce::new(),
65+
}
66+
}
67+
}
68+
5669
#[salsa_macros::db]
5770
impl salsa::Database for TestDB {}
5871

@@ -117,6 +130,10 @@ impl SourceDatabase for TestDB {
117130
fn crates_map(&self) -> Arc<CratesMap> {
118131
self.crates_map.clone()
119132
}
133+
134+
fn nonce_and_revision(&self) -> (Nonce, salsa::Revision) {
135+
(self.nonce, salsa::plumbing::ZalsaDatabase::zalsa(self).current_revision())
136+
}
120137
}
121138

122139
impl TestDB {

src/tools/rust-analyzer/crates/hir-ty/src/infer.rs

Lines changed: 40 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -88,54 +88,52 @@ pub(crate) use closure::{CaptureKind, CapturedItem, CapturedItemWithoutTy};
8888

8989
/// The entry point of type inference.
9090
pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<InferenceResult> {
91-
crate::next_solver::with_new_cache(|| {
92-
let _p = tracing::info_span!("infer_query").entered();
93-
let resolver = def.resolver(db);
94-
let body = db.body(def);
95-
let mut ctx = InferenceContext::new(db, def, &body, resolver);
96-
97-
match def {
98-
DefWithBodyId::FunctionId(f) => {
99-
ctx.collect_fn(f);
100-
}
101-
DefWithBodyId::ConstId(c) => ctx.collect_const(c, &db.const_signature(c)),
102-
DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_signature(s)),
103-
DefWithBodyId::VariantId(v) => {
104-
ctx.return_ty = TyBuilder::builtin(
105-
match db.enum_signature(v.lookup(db).parent).variant_body_type() {
106-
hir_def::layout::IntegerType::Pointer(signed) => match signed {
107-
true => BuiltinType::Int(BuiltinInt::Isize),
108-
false => BuiltinType::Uint(BuiltinUint::Usize),
109-
},
110-
hir_def::layout::IntegerType::Fixed(size, signed) => match signed {
111-
true => BuiltinType::Int(match size {
112-
Integer::I8 => BuiltinInt::I8,
113-
Integer::I16 => BuiltinInt::I16,
114-
Integer::I32 => BuiltinInt::I32,
115-
Integer::I64 => BuiltinInt::I64,
116-
Integer::I128 => BuiltinInt::I128,
117-
}),
118-
false => BuiltinType::Uint(match size {
119-
Integer::I8 => BuiltinUint::U8,
120-
Integer::I16 => BuiltinUint::U16,
121-
Integer::I32 => BuiltinUint::U32,
122-
Integer::I64 => BuiltinUint::U64,
123-
Integer::I128 => BuiltinUint::U128,
124-
}),
125-
},
91+
let _p = tracing::info_span!("infer_query").entered();
92+
let resolver = def.resolver(db);
93+
let body = db.body(def);
94+
let mut ctx = InferenceContext::new(db, def, &body, resolver);
95+
96+
match def {
97+
DefWithBodyId::FunctionId(f) => {
98+
ctx.collect_fn(f);
99+
}
100+
DefWithBodyId::ConstId(c) => ctx.collect_const(c, &db.const_signature(c)),
101+
DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_signature(s)),
102+
DefWithBodyId::VariantId(v) => {
103+
ctx.return_ty = TyBuilder::builtin(
104+
match db.enum_signature(v.lookup(db).parent).variant_body_type() {
105+
hir_def::layout::IntegerType::Pointer(signed) => match signed {
106+
true => BuiltinType::Int(BuiltinInt::Isize),
107+
false => BuiltinType::Uint(BuiltinUint::Usize),
126108
},
127-
);
128-
}
109+
hir_def::layout::IntegerType::Fixed(size, signed) => match signed {
110+
true => BuiltinType::Int(match size {
111+
Integer::I8 => BuiltinInt::I8,
112+
Integer::I16 => BuiltinInt::I16,
113+
Integer::I32 => BuiltinInt::I32,
114+
Integer::I64 => BuiltinInt::I64,
115+
Integer::I128 => BuiltinInt::I128,
116+
}),
117+
false => BuiltinType::Uint(match size {
118+
Integer::I8 => BuiltinUint::U8,
119+
Integer::I16 => BuiltinUint::U16,
120+
Integer::I32 => BuiltinUint::U32,
121+
Integer::I64 => BuiltinUint::U64,
122+
Integer::I128 => BuiltinUint::U128,
123+
}),
124+
},
125+
},
126+
);
129127
}
128+
}
130129

131-
ctx.infer_body();
130+
ctx.infer_body();
132131

133-
ctx.infer_mut_body();
132+
ctx.infer_mut_body();
134133

135-
ctx.infer_closures();
134+
ctx.infer_closures();
136135

137-
Arc::new(ctx.resolve_all())
138-
})
136+
Arc::new(ctx.resolve_all())
139137
}
140138

141139
pub(crate) fn infer_cycle_result(_: &dyn HirDatabase, _: DefWithBodyId) -> Arc<InferenceResult> {

src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,9 +2168,7 @@ pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result<Arc<Mi
21682168
let _p = tracing::info_span!("mir_body_query", ?detail).entered();
21692169
let body = db.body(def);
21702170
let infer = db.infer(def);
2171-
let mut result = crate::next_solver::with_new_cache(|| {
2172-
lower_to_mir(db, def, &body, &infer, body.body_expr)
2173-
})?;
2171+
let mut result = lower_to_mir(db, def, &body, &infer, body.body_expr)?;
21742172
result.shrink_to_fit();
21752173
Ok(Arc::new(result))
21762174
}

src/tools/rust-analyzer/crates/hir-ty/src/next_solver/interner.rs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,37 +2149,48 @@ TrivialTypeTraversalImpls! {
21492149
Placeholder<BoundVar>,
21502150
}
21512151

2152-
pub(crate) use tls_cache::with_new_cache;
21532152
mod tls_cache {
21542153
use crate::db::HirDatabase;
21552154

21562155
use super::DbInterner;
2156+
use base_db::Nonce;
21572157
use rustc_type_ir::search_graph::GlobalCache;
2158+
use salsa::Revision;
21582159
use std::cell::RefCell;
21592160

2160-
scoped_tls::scoped_thread_local!(static GLOBAL_CACHE: RefCell<rustc_type_ir::search_graph::GlobalCache<DbInterner<'static>>>);
2161+
struct Cache {
2162+
cache: GlobalCache<DbInterner<'static>>,
2163+
revision: Revision,
2164+
db_nonce: Nonce,
2165+
}
21612166

2162-
pub(crate) fn with_new_cache<T>(f: impl FnOnce() -> T) -> T {
2163-
GLOBAL_CACHE.set(&RefCell::new(GlobalCache::default()), f)
2167+
thread_local! {
2168+
static GLOBAL_CACHE: RefCell<Option<Cache>> = const { RefCell::new(None) };
21642169
}
21652170

21662171
pub(super) fn with_cache<'db, T>(
2167-
_db: &'db dyn HirDatabase,
2172+
db: &'db dyn HirDatabase,
21682173
f: impl FnOnce(&mut GlobalCache<DbInterner<'db>>) -> T,
21692174
) -> T {
2170-
// SAFETY: No idea
2171-
let call = move |slot: &RefCell<_>| {
2175+
GLOBAL_CACHE.with_borrow_mut(|handle| {
2176+
let (db_nonce, revision) = db.nonce_and_revision();
2177+
let handle = match handle {
2178+
Some(handle) => {
2179+
if handle.revision != revision || db_nonce != handle.db_nonce {
2180+
*handle = Cache { cache: GlobalCache::default(), revision, db_nonce };
2181+
}
2182+
handle
2183+
}
2184+
None => handle.insert(Cache { cache: GlobalCache::default(), revision, db_nonce }),
2185+
};
2186+
2187+
// SAFETY: No idea
21722188
f(unsafe {
21732189
std::mem::transmute::<
21742190
&mut GlobalCache<DbInterner<'static>>,
21752191
&mut GlobalCache<DbInterner<'db>>,
2176-
>(&mut *slot.borrow_mut())
2192+
>(&mut handle.cache)
21772193
})
2178-
};
2179-
if GLOBAL_CACHE.is_set() {
2180-
GLOBAL_CACHE.with(call)
2181-
} else {
2182-
GLOBAL_CACHE.set(&RefCell::new(GlobalCache::default()), || GLOBAL_CACHE.with(call))
2183-
}
2194+
})
21842195
}
21852196
}

src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use std::{fmt, panic, sync::Mutex};
44

55
use base_db::{
6-
CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, RootQueryDb, SourceDatabase,
7-
SourceRoot, SourceRootId, SourceRootInput,
6+
CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Nonce, RootQueryDb,
7+
SourceDatabase, SourceRoot, SourceRootId, SourceRootInput,
88
};
99

1010
use hir_def::{ModuleId, db::DefDatabase, nameres::crate_def_map};
@@ -17,12 +17,12 @@ use test_utils::extract_annotations;
1717
use triomphe::Arc;
1818

1919
#[salsa_macros::db]
20-
#[derive(Clone)]
2120
pub(crate) struct TestDB {
2221
storage: salsa::Storage<Self>,
2322
files: Arc<base_db::Files>,
2423
crates_map: Arc<CratesMap>,
2524
events: Arc<Mutex<Option<Vec<salsa::Event>>>>,
25+
nonce: Nonce,
2626
}
2727

2828
impl Default for TestDB {
@@ -41,6 +41,7 @@ impl Default for TestDB {
4141
events,
4242
files: Default::default(),
4343
crates_map: Default::default(),
44+
nonce: Nonce::new(),
4445
};
4546
this.set_expand_proc_attr_macros_with_durability(true, Durability::HIGH);
4647
// This needs to be here otherwise `CrateGraphBuilder` panics.
@@ -50,6 +51,18 @@ impl Default for TestDB {
5051
}
5152
}
5253

54+
impl Clone for TestDB {
55+
fn clone(&self) -> Self {
56+
Self {
57+
storage: self.storage.clone(),
58+
files: self.files.clone(),
59+
crates_map: self.crates_map.clone(),
60+
events: self.events.clone(),
61+
nonce: Nonce::new(),
62+
}
63+
}
64+
}
65+
5366
impl fmt::Debug for TestDB {
5467
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5568
f.debug_struct("TestDB").finish()
@@ -109,6 +122,10 @@ impl SourceDatabase for TestDB {
109122
fn crates_map(&self) -> Arc<CratesMap> {
110123
self.crates_map.clone()
111124
}
125+
126+
fn nonce_and_revision(&self) -> (Nonce, salsa::Revision) {
127+
(self.nonce, salsa::plumbing::ZalsaDatabase::zalsa(self).current_revision())
128+
}
112129
}
113130

114131
#[salsa_macros::db]

src/tools/rust-analyzer/crates/ide-db/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ use salsa::Durability;
5151
use std::{fmt, mem::ManuallyDrop};
5252

5353
use base_db::{
54-
CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Files, RootQueryDb,
54+
CrateGraphBuilder, CratesMap, FileSourceRootInput, FileText, Files, Nonce, RootQueryDb,
5555
SourceDatabase, SourceRoot, SourceRootId, SourceRootInput, query_group,
5656
};
5757
use hir::{
@@ -83,6 +83,7 @@ pub struct RootDatabase {
8383
storage: ManuallyDrop<salsa::Storage<Self>>,
8484
files: Arc<Files>,
8585
crates_map: Arc<CratesMap>,
86+
nonce: Nonce,
8687
}
8788

8889
impl std::panic::RefUnwindSafe for RootDatabase {}
@@ -102,6 +103,7 @@ impl Clone for RootDatabase {
102103
storage: self.storage.clone(),
103104
files: self.files.clone(),
104105
crates_map: self.crates_map.clone(),
106+
nonce: Nonce::new(),
105107
}
106108
}
107109
}
@@ -165,6 +167,10 @@ impl SourceDatabase for RootDatabase {
165167
fn crates_map(&self) -> Arc<CratesMap> {
166168
self.crates_map.clone()
167169
}
170+
171+
fn nonce_and_revision(&self) -> (Nonce, salsa::Revision) {
172+
(self.nonce, salsa::plumbing::ZalsaDatabase::zalsa(self).current_revision())
173+
}
168174
}
169175

170176
impl Default for RootDatabase {
@@ -179,6 +185,7 @@ impl RootDatabase {
179185
storage: ManuallyDrop::new(salsa::Storage::default()),
180186
files: Default::default(),
181187
crates_map: Default::default(),
188+
nonce: Nonce::new(),
182189
};
183190
// This needs to be here otherwise `CrateGraphBuilder` will panic.
184191
db.set_all_crates(Arc::new(Box::new([])));

0 commit comments

Comments
 (0)