Skip to content

Commit 32e3d7e

Browse files
leosvelperezFrozenPandaz
authored andcommitted
cleanup(core): cache compiled glob sets to avoid redundant recompilation (#34602)
## Current Behavior `build_glob_set` recompiles identical glob pattern sets on every call, even when the same set of patterns has been compiled before. ## Expected Behavior Compiled `NxGlobSet` instances are cached in a static `DashMap` keyed by sorted glob strings. Repeated calls with the same patterns return a shared `Arc<NxGlobSet>` instead of recompiling. Profiling `nx run-many -t build lint test --parallel 8` in the Nx repo measured 95.6% cache hit rate (9,758 of 10,202 calls) with only 444 unique pattern sets, reducing hashing-phase CPU by ~40%. (cherry picked from commit 12812dc)
1 parent f1d444e commit 32e3d7e

3 files changed

Lines changed: 29 additions & 17 deletions

File tree

packages/nx/src/native/glob.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ mod glob_parser;
44
pub mod glob_transform;
55

66
use crate::native::glob::glob_transform::convert_glob;
7+
use dashmap::DashMap;
78
use globset::{GlobBuilder, GlobSet, GlobSetBuilder};
89
use std::fmt::Debug;
910
use std::path::Path;
11+
use std::sync::{Arc, LazyLock};
1012
use tracing::trace;
1113

14+
static GLOB_CACHE: LazyLock<DashMap<String, Arc<NxGlobSet>>> = LazyLock::new(DashMap::new);
15+
1216
pub struct NxGlobSetBuilder {
1317
included_globs: GlobSetBuilder,
1418
excluded_globs: GlobSetBuilder,
@@ -89,7 +93,16 @@ fn potential_glob_split(
8993
}
9094
}
9195

92-
pub(crate) fn build_glob_set<S: AsRef<str> + Debug>(globs: &[S]) -> anyhow::Result<NxGlobSet> {
96+
pub(crate) fn build_glob_set<S: AsRef<str> + Debug>(globs: &[S]) -> anyhow::Result<Arc<NxGlobSet>> {
97+
// Build cache key from sorted globs joined by null byte (cannot appear in glob strings)
98+
let mut sorted_globs: Vec<&str> = globs.iter().map(|s| s.as_ref()).collect();
99+
sorted_globs.sort();
100+
let cache_key = sorted_globs.join("\0");
101+
102+
if let Some(cached) = GLOB_CACHE.get(&cache_key) {
103+
return Ok(Arc::clone(cached.value()));
104+
}
105+
93106
let result = globs
94107
.iter()
95108
.flat_map(|s| potential_glob_split(s.as_ref()))
@@ -106,7 +119,9 @@ pub(crate) fn build_glob_set<S: AsRef<str> + Debug>(globs: &[S]) -> anyhow::Resu
106119

107120
trace!(?globs, ?result, "converted globs");
108121

109-
NxGlobSetBuilder::new(&result)?.build()
122+
let glob_set = Arc::new(NxGlobSetBuilder::new(&result)?.build()?);
123+
GLOB_CACHE.insert(cache_key, Arc::clone(&glob_set));
124+
Ok(glob_set)
110125
}
111126

112127
pub(crate) fn contains_glob_pattern(value: &str) -> bool {

packages/nx/src/native/utils/find_matching_projects.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,16 @@ fn add_matching_projects_by_name<'a>(
160160
return Ok(());
161161
}
162162

163-
get_matching_strings(
164-
pattern.value,
165-
&build_glob_set(&[pattern.value])?,
166-
project_names,
167-
)
168-
.iter()
169-
.for_each(|item| {
170-
if pattern.exclude {
171-
matched_projects.remove(item);
172-
} else {
173-
matched_projects.insert(item);
174-
}
175-
});
163+
let glob = build_glob_set(&[pattern.value])?;
164+
get_matching_strings(pattern.value, &glob, project_names)
165+
.iter()
166+
.for_each(|item| {
167+
if pattern.exclude {
168+
matched_projects.remove(item);
169+
} else {
170+
matched_projects.insert(item);
171+
}
172+
});
176173

177174
Ok(())
178175
}

packages/nx/src/native/watch/watcher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use watchexec_events::{Event, FileType, Priority, Tag};
2727
use watchexec_signals::Signal;
2828

2929
/// Build the hardcoded ignore GlobSet used to check if new directories should be watched.
30-
fn build_ignore_glob_set() -> NxGlobSet {
30+
fn build_ignore_glob_set() -> Arc<NxGlobSet> {
3131
build_glob_set(HARDCODED_IGNORE_PATTERNS).expect("These static ignores always build")
3232
}
3333

@@ -180,7 +180,7 @@ impl Watcher {
180180
// Hardcoded ignore patterns for dynamic directory registration.
181181
// These are always applied regardless of use_ignore - we never want to watch
182182
// node_modules, .git, etc. even when watching task outputs.
183-
let ignore_globs: Arc<NxGlobSet> = Arc::new(build_ignore_glob_set());
183+
let ignore_globs: Arc<NxGlobSet> = build_ignore_glob_set();
184184

185185
let origin = self.origin.clone();
186186
let watch_exec_for_action = self.watch_exec.clone();

0 commit comments

Comments
 (0)