Skip to content

Commit 2098f8a

Browse files
authored
perf: use papaya instead of dashmap (#356)
1 parent 2bc5173 commit 2098f8a

File tree

3 files changed

+75
-99
lines changed

3 files changed

+75
-99
lines changed

Cargo.lock

Lines changed: 24 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ name = "resolver"
6464

6565
[dependencies]
6666
cfg-if = "1"
67-
dashmap = { version = "6", features = ["raw-api"] }
6867
indexmap = { version = "2", features = ["serde"] }
6968
json-strip-comments = "1"
7069
once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
70+
papaya = "0.1.8"
7171
rustc-hash = { version = "2" }
72+
seize = { version = "0.4" }
7273
serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
7374
serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
7475
simdutf8 = { version = "0.1" }

src/fs_cache.rs

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use std::{
1313
};
1414

1515
use cfg_if::cfg_if;
16-
use dashmap::{DashMap, DashSet};
1716
use once_cell::sync::OnceCell as OnceLock;
17+
use papaya::{Equivalent, HashMap, HashSet};
1818
use rustc_hash::FxHasher;
1919

2020
use crate::{
@@ -38,50 +38,16 @@ thread_local! {
3838
#[derive(Default)]
3939
pub struct FsCache<Fs> {
4040
pub(crate) fs: Fs,
41-
paths: DashSet<PathEntry<'static>, BuildHasherDefault<IdentityHasher>>,
42-
tsconfigs: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
41+
paths: HashSet<FsCachedPath, BuildHasherDefault<IdentityHasher>>,
42+
tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
4343
}
4444

45-
/// An entry in the path cache. Can also be borrowed for lookups without allocations.
46-
enum PathEntry<'a> {
47-
Owned(FsCachedPath),
48-
Borrowed { hash: u64, path: &'a Path },
49-
}
50-
51-
impl Hash for PathEntry<'_> {
52-
fn hash<H: Hasher>(&self, state: &mut H) {
53-
match self {
54-
PathEntry::Owned(entry) => {
55-
entry.hash.hash(state);
56-
}
57-
PathEntry::Borrowed { hash, .. } => {
58-
hash.hash(state);
59-
}
60-
}
61-
}
62-
}
63-
64-
impl PartialEq for PathEntry<'_> {
65-
fn eq(&self, other: &Self) -> bool {
66-
let self_path = match self {
67-
PathEntry::Owned(info) => &info.path,
68-
PathEntry::Borrowed { path, .. } => *path,
69-
};
70-
let other_path = match other {
71-
PathEntry::Owned(info) => &info.path,
72-
PathEntry::Borrowed { path, .. } => *path,
73-
};
74-
self_path.as_os_str() == other_path.as_os_str()
75-
}
76-
}
77-
impl Eq for PathEntry<'_> {}
78-
7945
impl<Fs: FileSystem> Cache for FsCache<Fs> {
8046
type Cp = FsCachedPath;
8147

8248
fn clear(&self) {
83-
self.paths.clear();
84-
self.tsconfigs.clear();
49+
self.paths.pin().clear();
50+
self.tsconfigs.pin().clear();
8551
}
8652

8753
#[allow(clippy::cast_possible_truncation)]
@@ -93,25 +59,17 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
9359
path.as_os_str().hash(&mut hasher);
9460
hasher.finish()
9561
};
96-
let key = PathEntry::Borrowed { hash, path };
97-
// A DashMap is just an array of RwLock<HashSet>, sharded by hash to reduce lock contention.
98-
// This uses the low level raw API to avoid cloning the value when using the `entry` method.
99-
// First, find which shard the value is in, and check to see if we already have a value in the map.
100-
let shard = self.paths.determine_shard(hash as usize);
101-
{
102-
// Scope the read lock.
103-
let map = self.paths.shards()[shard].read();
104-
if let Some((PathEntry::Owned(entry), _)) = map.get(hash, |v| v.0 == key) {
105-
return entry.clone();
106-
}
62+
let paths = self.paths.pin();
63+
if let Some(entry) = paths.get(&BorrowedCachedPath { hash, path }) {
64+
return entry.clone();
10765
}
10866
let parent = path.parent().map(|p| self.value(p));
10967
let cached_path = FsCachedPath(Arc::new(CachedPathImpl::new(
11068
hash,
11169
path.to_path_buf().into_boxed_path(),
11270
parent,
11371
)));
114-
self.paths.insert(PathEntry::Owned(cached_path.clone()));
72+
paths.insert(cached_path.clone());
11573
cached_path
11674
}
11775

@@ -196,8 +154,9 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
196154
path: &Path,
197155
callback: F, // callback for modifying tsconfig with `extends`
198156
) -> Result<Arc<TsConfig>, ResolveError> {
199-
if let Some(tsconfig) = self.tsconfigs.get(path) {
200-
return Ok(Arc::clone(&tsconfig));
157+
let tsconfigs = self.tsconfigs.pin();
158+
if let Some(tsconfig) = tsconfigs.get(path) {
159+
return Ok(Arc::clone(tsconfig));
201160
}
202161
let meta = self.fs.metadata(path).ok();
203162
let tsconfig_path = if meta.is_some_and(|m| m.is_file) {
@@ -219,14 +178,26 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
219178
})?;
220179
callback(&mut tsconfig)?;
221180
let tsconfig = Arc::new(tsconfig.build());
222-
self.tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
181+
tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
223182
Ok(tsconfig)
224183
}
225184
}
226185

227186
impl<Fs: FileSystem> FsCache<Fs> {
228187
pub fn new(fs: Fs) -> Self {
229-
Self { fs, paths: DashSet::default(), tsconfigs: DashMap::default() }
188+
Self {
189+
fs,
190+
paths: HashSet::builder()
191+
.hasher(BuildHasherDefault::default())
192+
.resize_mode(papaya::ResizeMode::Blocking)
193+
.collector(seize::Collector::new().epoch_frequency(None))
194+
.build(),
195+
tsconfigs: HashMap::builder()
196+
.hasher(BuildHasherDefault::default())
197+
.resize_mode(papaya::ResizeMode::Blocking)
198+
.collector(seize::Collector::new().epoch_frequency(None))
199+
.build(),
200+
}
230201
}
231202

232203
/// Returns the canonical path, resolving all symbolic links.
@@ -479,6 +450,29 @@ impl PartialEq for FsCachedPath {
479450

480451
impl Eq for FsCachedPath {}
481452

453+
struct BorrowedCachedPath<'a> {
454+
hash: u64,
455+
path: &'a Path,
456+
}
457+
458+
impl Equivalent<FsCachedPath> for BorrowedCachedPath<'_> {
459+
fn equivalent(&self, other: &FsCachedPath) -> bool {
460+
self.path.as_os_str() == other.path.as_os_str()
461+
}
462+
}
463+
464+
impl Hash for BorrowedCachedPath<'_> {
465+
fn hash<H: Hasher>(&self, state: &mut H) {
466+
self.hash.hash(state);
467+
}
468+
}
469+
470+
impl PartialEq for BorrowedCachedPath<'_> {
471+
fn eq(&self, other: &Self) -> bool {
472+
self.path.as_os_str() == other.path.as_os_str()
473+
}
474+
}
475+
482476
/// Since the cache key is memoized, use an identity hasher
483477
/// to avoid double cache.
484478
#[derive(Default)]

0 commit comments

Comments
 (0)