Skip to content

Commit ee7b084

Browse files
Eh2406mpizenberg
andauthored
perf: index incompatibilities by package (#84)
* refactor: dont copy the term for Contradicted * perf: index incompatibilities by package * refactor: move merge_into into core * refactor: rename merge_into to merge_incompatibility Co-authored-by: Matthieu Pizenberg <matthieu.pizenberg@gmail.com>
1 parent 6be0fbf commit ee7b084

File tree

3 files changed

+44
-48
lines changed

3 files changed

+44
-48
lines changed

src/internal/core.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
1414
use crate::package::Package;
1515
use crate::report::DerivationTree;
1616
use crate::solver::DependencyConstraints;
17+
use crate::type_aliases::Map;
1718
use crate::version::Version;
1819

1920
/// Current state of the PubGrub algorithm.
@@ -22,8 +23,7 @@ pub struct State<P: Package, V: Version> {
2223
root_package: P,
2324
root_version: V,
2425

25-
/// TODO: remove pub.
26-
pub incompatibilities: Vec<IncompId<P, V>>,
26+
incompatibilities: Map<P, Vec<IncompId<P, V>>>,
2727

2828
/// Partial solution.
2929
/// TODO: remove pub.
@@ -46,10 +46,12 @@ impl<P: Package, V: Version> State<P, V> {
4646
root_package.clone(),
4747
root_version.clone(),
4848
));
49+
let mut incompatibilities = Map::default();
50+
incompatibilities.insert(root_package.clone(), vec![not_root_id]);
4951
Self {
5052
root_package,
5153
root_version,
52-
incompatibilities: vec![not_root_id],
54+
incompatibilities,
5355
partial_solution: PartialSolution::empty(),
5456
incompatibility_store,
5557
unit_propagation_buffer: vec![],
@@ -58,10 +60,8 @@ impl<P: Package, V: Version> State<P, V> {
5860

5961
/// Add an incompatibility to the state.
6062
pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, V>) {
61-
Incompatibility::merge_into(
62-
self.incompatibility_store.alloc(incompat),
63-
&mut self.incompatibilities,
64-
);
63+
let id = self.incompatibility_store.alloc(incompat);
64+
self.merge_incompatibility(id);
6565
}
6666

6767
/// Add an incompatibility to the state.
@@ -79,7 +79,7 @@ impl<P: Package, V: Version> State<P, V> {
7979
}));
8080
// Merge the newly created incompatibilities with the older ones.
8181
for id in IncompId::range_to_iter(new_incompats_id_range.clone()) {
82-
Incompatibility::merge_into(id, &mut self.incompatibilities);
82+
self.merge_incompatibility(id);
8383
}
8484
new_incompats_id_range
8585
}
@@ -98,12 +98,9 @@ impl<P: Package, V: Version> State<P, V> {
9898
// Iterate over incompatibilities in reverse order
9999
// to evaluate first the newest incompatibilities.
100100
let mut conflict_id = None;
101-
for &incompat_id in self.incompatibilities.iter().rev() {
101+
// We only care about incompatibilities if it contains the current package.
102+
for &incompat_id in self.incompatibilities[&current_package].iter().rev() {
102103
let current_incompat = &self.incompatibility_store[incompat_id];
103-
// We only care about that incompatibility if it contains the current package.
104-
if current_incompat.get(&current_package).is_none() {
105-
continue;
106-
}
107104
match self.partial_solution.relation(current_incompat) {
108105
// If the partial solution satisfies the incompatibility
109106
// we must perform conflict resolution.
@@ -204,7 +201,35 @@ impl<P: Package, V: Version> State<P, V> {
204201
self.partial_solution
205202
.backtrack(decision_level, &self.incompatibility_store);
206203
if incompat_changed {
207-
Incompatibility::merge_into(incompat, &mut self.incompatibilities);
204+
self.merge_incompatibility(incompat);
205+
}
206+
}
207+
208+
/// Add this incompatibility into the set of all incompatibilities.
209+
///
210+
/// Pub collapses identical dependencies from adjacent package versions
211+
/// into individual incompatibilities.
212+
/// This substantially reduces the total number of incompatibilities
213+
/// and makes it much easier for Pub to reason about multiple versions of packages at once.
214+
///
215+
/// For example, rather than representing
216+
/// foo 1.0.0 depends on bar ^1.0.0 and
217+
/// foo 1.1.0 depends on bar ^1.0.0
218+
/// as two separate incompatibilities,
219+
/// they are collapsed together into the single incompatibility {foo ^1.0.0, not bar ^1.0.0}
220+
/// (provided that no other version of foo exists between 1.0.0 and 2.0.0).
221+
/// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 }
222+
/// without having to check the existence of other versions though.
223+
///
224+
/// Here we do the simple stupid thing of just growing the Vec.
225+
/// It may not be trivial since those incompatibilities
226+
/// may already have derived others.
227+
fn merge_incompatibility(&mut self, id: IncompId<P, V>) {
228+
for (pkg, _term) in self.incompatibility_store[id].iter() {
229+
self.incompatibilities
230+
.entry(pkg.clone())
231+
.or_default()
232+
.push(id);
208233
}
209234
}
210235

src/internal/incompatibility.rs

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,16 @@ enum Kind<P: Package, V: Version> {
5252
DerivedFrom(IncompId<P, V>, IncompId<P, V>),
5353
}
5454

55-
/// A type alias for a pair of [Package] and a corresponding [Term].
56-
pub type PackageTerm<P, V> = (P, Term<V>);
57-
5855
/// A Relation describes how a set of terms can be compared to an incompatibility.
5956
/// Typically, the set of terms comes from the partial solution.
6057
#[derive(Eq, PartialEq)]
61-
pub enum Relation<P: Package, V: Version> {
58+
pub enum Relation<P: Package> {
6259
/// We say that a set of terms S satisfies an incompatibility I
6360
/// if S satisfies every term in I.
6461
Satisfied,
6562
/// We say that S contradicts I
6663
/// if S contradicts at least one term in I.
67-
Contradicted(PackageTerm<P, V>),
64+
Contradicted(P),
6865
/// If S satisfies all but one of I's terms and is inconclusive for the remaining term,
6966
/// we say S "almost satisfies" I and we call the remaining term the "unsatisfied term".
7067
AlmostSatisfied(P),
@@ -121,32 +118,6 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
121118
}
122119
}
123120

124-
/// Add this incompatibility into the set of all incompatibilities.
125-
///
126-
/// Pub collapses identical dependencies from adjacent package versions
127-
/// into individual incompatibilities.
128-
/// This substantially reduces the total number of incompatibilities
129-
/// and makes it much easier for Pub to reason about multiple versions of packages at once.
130-
///
131-
/// For example, rather than representing
132-
/// foo 1.0.0 depends on bar ^1.0.0 and
133-
/// foo 1.1.0 depends on bar ^1.0.0
134-
/// as two separate incompatibilities,
135-
/// they are collapsed together into the single incompatibility {foo ^1.0.0, not bar ^1.0.0}
136-
/// (provided that no other version of foo exists between 1.0.0 and 2.0.0).
137-
/// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 }
138-
/// without having to check the existence of other versions though.
139-
/// And it would even keep the same [Kind]: [FromDependencyOf](Kind::FromDependencyOf) foo.
140-
///
141-
/// Here we do the simple stupid thing of just growing the Vec.
142-
/// TODO: improve this.
143-
/// It may not be trivial since those incompatibilities
144-
/// may already have derived others.
145-
/// Maybe this should not be pursued.
146-
pub fn merge_into(id: Id<Self>, incompatibilities: &mut Vec<Id<Self>>) {
147-
incompatibilities.push(id);
148-
}
149-
150121
/// Prior cause of two incompatibilities using the rule of resolution.
151122
pub fn prior_cause(
152123
incompat: Id<Self>,
@@ -173,13 +144,13 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
173144
}
174145

175146
/// CF definition of Relation enum.
176-
pub fn relation(&self, mut terms: impl FnMut(&P) -> Option<Term<V>>) -> Relation<P, V> {
147+
pub fn relation(&self, mut terms: impl FnMut(&P) -> Option<Term<V>>) -> Relation<P> {
177148
let mut relation = Relation::Satisfied;
178149
for (package, incompat_term) in self.package_terms.iter() {
179150
match terms(package).map(|term| incompat_term.relation_with(&term)) {
180151
Some(term::Relation::Satisfied) => {}
181152
Some(term::Relation::Contradicted) => {
182-
return Relation::Contradicted((package.clone(), incompat_term.clone()));
153+
return Relation::Contradicted(package.clone());
183154
}
184155
None | Some(term::Relation::Inconclusive) => {
185156
// If a package is not present, the intersection is the same as [Term::any].

src/internal/partial_solution.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
169169
}
170170

171171
/// Check if the terms in the partial solution satisfy the incompatibility.
172-
pub fn relation(&mut self, incompat: &Incompatibility<P, V>) -> Relation<P, V> {
172+
pub fn relation(&mut self, incompat: &Incompatibility<P, V>) -> Relation<P> {
173173
incompat.relation(|package| self.memory.term_intersection_for_package(package).cloned())
174174
}
175175

0 commit comments

Comments
 (0)