mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-24 16:46:06 -04:00
perf(pacquet/resolving-deps-resolver): share ResolveResult via Arc on tree + graph nodes
`ResolveResult.clone()` ran at every visit in `resolve_node` (into `ResolvedPackage.result`) and again per `(dep_path, peer-suffix)` slot when `resolve_peers` carved the graph nodes out of the resolved tree (into `DependenciesGraphNode.resolve_result`). Both clones deep-copied every `String` field on `ResolveResult` (`id`, `alias`, `resolved_via`, `name_ver`, etc.) — 2× per resolved package × ~1.3k packages = ~2.7k full struct clones on the resolve hot path. Wrap once at the resolver boundary so both stores share one heap allocation: - `ResolvedPackage.result` and `DependenciesGraphNode.resolve_result` re-typed as `Arc<ResolveResult>`. - `resolve_node` wraps the resolver-returned `ResolveResult` in `Arc::new` once and uses `Arc::clone` for the store + later reads. The `extract_children` / `extract_peer_dependencies` borrows work unchanged via `Arc`'s `Deref`. - `resolve_peers`'s `.clone()` of `pkg.result` is now an `Arc::clone` (refcount bump), not a deep copy. Read sites in `dependencies_graph_to_lockfile` and `install_with_fresh_lockfile` work unchanged — `&Arc<T>` derefs to `&T` at the field-init coercion site and at every `.field`-style access. Tests: 348/348 in `pacquet-resolving-deps-resolver` + `pacquet-package-manager` pass. Clippy clean.
This commit is contained in:
@@ -74,7 +74,7 @@ fn make_node_with_optional(
|
||||
DependenciesGraphNode {
|
||||
dep_path: dep_path.clone(),
|
||||
resolved_package_id: format!("{name}@{version}"),
|
||||
resolve_result: make_resolve_result(name, version, manifest),
|
||||
resolve_result: std::sync::Arc::new(make_resolve_result(name, version, manifest)),
|
||||
children,
|
||||
peer_dependencies,
|
||||
transitive_peer_dependencies,
|
||||
@@ -265,7 +265,7 @@ fn peer_suffixed_dep_path_splits_into_distinct_snapshot_and_package_keys() {
|
||||
let react_dom = DependenciesGraphNode {
|
||||
dep_path: react_dom_dep_path.clone(),
|
||||
resolved_package_id: "react-dom@17.0.2".to_string(),
|
||||
resolve_result: make_resolve_result(
|
||||
resolve_result: std::sync::Arc::new(make_resolve_result(
|
||||
"react-dom",
|
||||
"17.0.2",
|
||||
json!({
|
||||
@@ -273,7 +273,7 @@ fn peer_suffixed_dep_path_splits_into_distinct_snapshot_and_package_keys() {
|
||||
"version": "17.0.2",
|
||||
"peerDependencies": { "react": "17.0.2" },
|
||||
}),
|
||||
),
|
||||
)),
|
||||
children: react_dom_children,
|
||||
peer_dependencies: react_dom_peers,
|
||||
transitive_peer_dependencies: HashSet::new(),
|
||||
|
||||
@@ -23,7 +23,12 @@ pub struct DependenciesGraphNode {
|
||||
/// by reference value (cloned) so consumers don't need a separate
|
||||
/// map lookup.
|
||||
pub resolved_package_id: String,
|
||||
pub resolve_result: ResolveResult,
|
||||
/// Held as `Arc` so the graph's per-occurrence clones (one per
|
||||
/// `(dep_path, peer-suffix)` slot) reuse the same heap-allocated
|
||||
/// `ResolveResult` instead of deep-copying each field. The graph
|
||||
/// is built once and read by the install dispatch; nothing
|
||||
/// mutates the inner `ResolveResult` after `resolve_peers`.
|
||||
pub resolve_result: std::sync::Arc<ResolveResult>,
|
||||
/// `alias → DepPath` edges to children + resolved peers. Children
|
||||
/// inherited from the per-occurrence tree node, peers added during
|
||||
/// peer resolution.
|
||||
|
||||
@@ -348,6 +348,11 @@ where
|
||||
specifier: render_specifier(&wanted),
|
||||
});
|
||||
};
|
||||
// Wrap in `Arc` once so the two store sites below (the per-id
|
||||
// `ResolvedPackage` envelope and the later peer-resolved graph
|
||||
// node) share one heap-allocated `ResolveResult` instead of
|
||||
// cloning every `String` field per occurrence.
|
||||
let result = Arc::new(result);
|
||||
|
||||
if let Some(violation) = result.policy_violation.clone() {
|
||||
lock_recoverable(&ctx.policy_violations).push(violation);
|
||||
@@ -403,7 +408,7 @@ where
|
||||
id.clone(),
|
||||
ResolvedPackage {
|
||||
id: id.clone(),
|
||||
result: result.clone(),
|
||||
result: Arc::clone(&result),
|
||||
peer_dependencies,
|
||||
optional: current_is_optional,
|
||||
},
|
||||
|
||||
@@ -80,7 +80,13 @@ pub struct DirectDep {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResolvedPackage {
|
||||
pub id: String,
|
||||
pub result: ResolveResult,
|
||||
/// Held as `Arc` so cloning a `ResolvedPackage` (which the
|
||||
/// per-occurrence tree walk does on every snapshot, and which
|
||||
/// the peer-resolution pass does when it carves
|
||||
/// `DependenciesGraphNode`s out of the resolved tree) is an
|
||||
/// `Arc::clone` instead of a deep copy of every `String` field
|
||||
/// on `ResolveResult` (id, alias, resolved_via, name_ver, …).
|
||||
pub result: std::sync::Arc<ResolveResult>,
|
||||
/// `peerDependencies` from the package's manifest, with names that
|
||||
/// also appear in the package's own `dependencies` /
|
||||
/// `optionalDependencies` filtered out (mirrors upstream's
|
||||
|
||||
Reference in New Issue
Block a user