Skip to content

Commit a1c0656

Browse files
authored
perf: Replace O(N) workspace path lookup with HashMap index in DependencySplitter (#11922)
## Summary `DependencySplitter::workspace()` did a linear scan over all workspaces to find a package by its directory path. This is called for every `workspace:` path-based dependency during `connect_internal_dependencies` — O(N) per lookup, O(N²) total for repos using path-based workspace protocol references (e.g. `workspace:../sibling-pkg`). Adds `WorkspacePathIndex` — a `HashMap<&AnchoredSystemPath, &PackageName>` built once before the dependency resolution loop and shared across all `DependencySplitter` instances. Lookups go from O(N) linear scan to O(1). ### Changes **`dep_splitter.rs`**: New `WorkspacePathIndex` struct built from the workspaces HashMap. `DependencySplitter::new` takes a shared `&WorkspacePathIndex` reference. `workspace()` uses an O(1) HashMap lookup instead of `iter().find()`. **`builder.rs`**: `connect_internal_dependencies` builds the `WorkspacePathIndex` once before the loop. `Dependencies::new` passes it through to the splitter.
1 parent f8717d4 commit a1c0656

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

crates/turborepo-repository/src/package_graph/builder.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use turborepo_graph_utils as graph;
1010
use turborepo_lockfiles::Lockfile;
1111

1212
use super::{
13-
PackageGraph, PackageInfo, PackageName, PackageNode, dep_splitter::DependencySplitter,
13+
PackageGraph, PackageInfo, PackageName, PackageNode,
14+
dep_splitter::{DependencySplitter, WorkspacePathIndex},
1415
};
1516
use crate::{
1617
discovery::{
@@ -392,6 +393,7 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedWorkspaces, T> {
392393
&mut self,
393394
package_manager: &PackageManager,
394395
) -> Result<(), Error> {
396+
let path_index = WorkspacePathIndex::new(&self.workspaces);
395397
let split_deps = self
396398
.workspaces
397399
.iter()
@@ -405,6 +407,7 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedWorkspaces, T> {
405407
&self.workspaces,
406408
package_manager,
407409
entry.package_json.all_dependencies(),
410+
&path_index,
408411
),
409412
)
410413
})
@@ -601,15 +604,21 @@ impl Dependencies {
601604
workspaces: &HashMap<PackageName, PackageInfo>,
602605
package_manager: &PackageManager,
603606
dependencies: I,
607+
path_index: &WorkspacePathIndex<'_>,
604608
) -> Self {
605609
let resolved_workspace_json_path = repo_root.resolve(workspace_json_path);
606610
let workspace_dir = resolved_workspace_json_path
607611
.parent()
608612
.expect("package.json path should have parent");
609613
let mut internal = HashSet::new();
610614
let mut external = BTreeMap::new();
611-
let splitter =
612-
DependencySplitter::new(repo_root, workspace_dir, workspaces, package_manager);
615+
let splitter = DependencySplitter::new(
616+
repo_root,
617+
workspace_dir,
618+
workspaces,
619+
package_manager,
620+
path_index,
621+
);
613622
for (name, version) in dependencies.into_iter() {
614623
if let Some(workspace) = splitter.is_internal(name, version) {
615624
internal.insert(workspace);

crates/turborepo-repository/src/package_graph/dep_splitter.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,26 @@ use turbopath::{
88
use super::{PackageInfo, PackageName};
99
use crate::package_manager::PackageManager;
1010

11+
/// Reverse index from package path to package name, built once and shared
12+
/// across all `DependencySplitter` instances.
13+
pub struct WorkspacePathIndex<'a>(HashMap<&'a AnchoredSystemPath, &'a PackageName>);
14+
15+
impl<'a> WorkspacePathIndex<'a> {
16+
pub fn new(workspaces: &'a HashMap<PackageName, PackageInfo>) -> Self {
17+
Self(
18+
workspaces
19+
.iter()
20+
.map(|(name, info)| (info.package_path(), name))
21+
.collect(),
22+
)
23+
}
24+
}
25+
1126
pub struct DependencySplitter<'a> {
1227
repo_root: &'a AbsoluteSystemPath,
1328
workspace_dir: &'a AbsoluteSystemPath,
1429
workspaces: &'a HashMap<PackageName, PackageInfo>,
30+
path_index: &'a WorkspacePathIndex<'a>,
1531
link_workspace_packages: bool,
1632
}
1733

@@ -21,12 +37,14 @@ impl<'a> DependencySplitter<'a> {
2137
workspace_dir: &'a AbsoluteSystemPath,
2238
workspaces: &'a HashMap<PackageName, PackageInfo>,
2339
package_manager: &PackageManager,
40+
path_index: &'a WorkspacePathIndex<'a>,
2441
) -> Self {
2542
let link_workspace_packages = package_manager.link_workspace_packages(repo_root);
2643
Self {
2744
repo_root,
2845
workspace_dir,
2946
workspaces,
47+
path_index,
3048
link_workspace_packages,
3149
}
3250
}
@@ -86,9 +104,9 @@ impl<'a> DependencySplitter<'a> {
86104
}
87105

88106
fn workspace(&self, path: &AnchoredSystemPath) -> Option<(&PackageName, &PackageInfo)> {
89-
self.workspaces
90-
.iter()
91-
.find(|(_, info)| info.package_path() == path)
107+
let name = self.path_index.0.get(path)?;
108+
let info = self.workspaces.get(name)?;
109+
Some((name, info))
92110
}
93111
}
94112

@@ -305,10 +323,12 @@ mod test {
305323
map
306324
};
307325

326+
let path_index = WorkspacePathIndex::new(&workspaces);
308327
let splitter = DependencySplitter {
309328
repo_root: &root,
310329
workspace_dir: &pkg_dir,
311330
workspaces: &workspaces,
331+
path_index: &path_index,
312332
link_workspace_packages,
313333
};
314334

0 commit comments

Comments
 (0)