Skip to content

Commit ff62187

Browse files
authored
cleanup(core): reduce misc allocations (#34647)
## Current Behavior Several Rust native modules and one TS utility have unnecessary allocations: - `hash_planner.rs`: visited set uses `HashSet<String>` with `to_string()` per insert; unnecessary `.collect::<Vec<_>>()` creating temp vecs; no pre-allocation for dependency inputs - `context.rs`: `update_files` allocates a String per map entry inside retain loop - `hash_workspace_files.rs`: `.clone()` before `.as_bytes()` — 2 needless heap allocs per file - `find_matching_projects.rs`: collects HashMap keys into Vec + linear search instead of O(1) lookup - `validate_outputs.rs`: compiles regex on every call - `project-configuration-utils.ts`: uses `JSON.parse(JSON.stringify())` for deep cloning ## Expected Behavior All unnecessary allocations removed: - Borrow `&str` from project graph instead of cloning Strings into visited set - Use `Path::new()` once outside retain closure (also more correct — component-boundary-aware matching) - Call `.as_bytes()` directly without clone - Use `HashMap::get_key_value()` for O(1) lookup - Cache compiled regex with `LazyLock<Regex>` - Use `structuredClone` (Node 18+) instead of JSON round-trip See individual commits for detailed rationale per change.
1 parent 1a973ba commit ff62187

6 files changed

Lines changed: 28 additions & 27 deletions

File tree

packages/nx/src/native/cache/validate_outputs.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use itertools::Itertools;
22
use regex::Regex;
3+
use std::sync::LazyLock;
34

45
use crate::native::glob::{contains_glob_pattern, glob_transform::partition_glob};
56

67
const ALLOWED_WORKSPACE_ROOT_OUTPUT_PREFIXES: [&str; 2] = ["!{workspaceRoot}", "{workspaceRoot}"];
78

8-
fn is_missing_prefix(output: &str) -> bool {
9-
let re = Regex::new(r"^!?\{[\s\S]+\}").expect("Output pattern regex should compile");
9+
static OUTPUT_PREFIX_RE: LazyLock<Regex> =
10+
LazyLock::new(|| Regex::new(r"^!?\{[\s\S]+\}").expect("Output pattern regex should compile"));
1011

11-
!re.is_match(output)
12+
fn is_missing_prefix(output: &str) -> bool {
13+
!OUTPUT_PREFIX_RE.is_match(output)
1214
}
1315

1416
#[napi]

packages/nx/src/native/tasks/hash_planner.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl HashPlanner {
7373
&inputs,
7474
&task_graph,
7575
&external_deps_mapped,
76-
&mut Box::new(hashbrown::HashSet::from([task.target.project.to_string()])),
76+
&mut Box::new(hashbrown::HashSet::from([task.target.project.as_str()])),
7777
)?;
7878

7979
let mut inputs: Vec<HashInstruction> = target
@@ -246,18 +246,16 @@ impl HashPlanner {
246246
}
247247
}
248248

249-
fn self_and_deps_inputs(
250-
&self,
249+
fn self_and_deps_inputs<'a>(
250+
&'a self,
251251
project_name: &str,
252252
task: &Task,
253253
inputs: &SplitInputs,
254254
task_graph: &TaskGraph,
255-
external_deps_mapped: &hashbrown::HashMap<&String, Vec<&String>>,
256-
visited: &mut Box<hashbrown::HashSet<String>>,
255+
external_deps_mapped: &hashbrown::HashMap<&String, Vec<&'a String>>,
256+
visited: &mut Box<hashbrown::HashSet<&'a str>>,
257257
) -> anyhow::Result<Vec<HashInstruction>> {
258-
let project_deps = &self.project_graph.dependencies[project_name]
259-
.iter()
260-
.collect::<Vec<_>>();
258+
let project_deps = &self.project_graph.dependencies[project_name];
261259
let self_inputs = self.gather_self_inputs(project_name, &inputs.self_inputs);
262260
let deps_inputs = self.gather_dependency_inputs(
263261
task,
@@ -303,22 +301,23 @@ impl HashPlanner {
303301
task: &Task,
304302
inputs: &[Input],
305303
task_graph: &TaskGraph,
306-
project_deps: &[&'a String],
304+
project_deps: &'a [String],
307305
external_deps_mapped: &hashbrown::HashMap<&String, Vec<&'a String>>,
308-
visited: &mut Box<hashbrown::HashSet<String>>,
306+
visited: &mut Box<hashbrown::HashSet<&'a str>>,
309307
) -> anyhow::Result<Vec<HashInstruction>> {
310-
let mut deps_inputs: Vec<HashInstruction> = vec![];
308+
let mut deps_inputs: Vec<HashInstruction> =
309+
Vec::with_capacity(inputs.len() * project_deps.len());
311310

312311
for input in inputs {
313312
for dep in project_deps {
314-
if visited.contains(*dep) {
313+
if visited.contains(dep.as_str()) {
315314
continue;
316315
}
317-
visited.insert(dep.to_string());
316+
visited.insert(dep.as_str());
318317

319-
if self.project_graph.nodes.contains_key(*dep) {
318+
if self.project_graph.nodes.contains_key(dep) {
320319
let Some(dep_inputs) = get_inputs_for_dependency(
321-
&self.project_graph.nodes[*dep],
320+
&self.project_graph.nodes[dep],
322321
&self.nx_json,
323322
input,
324323
)?

packages/nx/src/native/tasks/hashers/hash_workspace_files.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ pub fn hash_workspace_files_with_inputs(
7373
.filter(|file| glob.is_match(&file.file))
7474
{
7575
debug!("Adding {:?} ({:?}) to hash", file.hash, file.file);
76-
hasher.update(file.file.clone().as_bytes());
77-
hasher.update(file.hash.clone().as_bytes());
76+
hasher.update(file.file.as_bytes());
77+
hasher.update(file.hash.as_bytes());
7878
files.push(file.file.clone());
7979
}
8080
let hashed_value = hasher.digest().to_string();

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,10 @@ fn add_matching_projects_by_name<'a>(
150150
pattern: &ProjectPattern,
151151
matched_projects: &mut HashSet<&'a str>,
152152
) -> anyhow::Result<()> {
153-
let keys = projects.keys().map(|k| k.as_str()).collect::<Vec<_>>();
154-
if let Some(project_name) = keys.iter().find(|k| *k == &pattern.value) {
153+
if let Some(project_name) = projects
154+
.get_key_value(pattern.value)
155+
.map(|(k, _)| k.as_str())
156+
{
155157
if pattern.exclude {
156158
matched_projects.remove(pattern.value);
157159
} else {

packages/nx/src/native/workspace/context.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,8 @@ impl FilesWorker {
165165
let removal = map.remove(&PathBuf::from(deleted_path));
166166
if removal.is_none() {
167167
// If the path is a directory, this retains only files not in the directory.
168-
map.retain(|path, _| {
169-
let owned_deleted_path = deleted_path.to_owned();
170-
!path.starts_with(owned_deleted_path + "/")
171-
});
168+
let prefix = Path::new(deleted_path);
169+
map.retain(|path, _| !path.starts_with(prefix));
172170
};
173171
}
174172

packages/nx/src/project-graph/utils/project-configuration-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ function targetDefaultShouldBeApplied(
908908
}
909909

910910
function deepClone(obj) {
911-
return JSON.parse(JSON.stringify(obj));
911+
return structuredClone(obj);
912912
}
913913

914914
export function mergeTargetDefaultWithTargetDefinition(

0 commit comments

Comments
 (0)