Skip to content

Commit 0f5c913

Browse files
Eh2406aleksator
andauthored
perf: precompute intersections (2x speedup) (#37)
* perf: precompute intersections (2x speedup) * style: fix grammar in function names Co-authored-by: Alex Tokarev <[email protected]>
1 parent ad4f48e commit 0f5c913

File tree

4 files changed

+84
-70
lines changed

4 files changed

+84
-70
lines changed

src/internal/incompatibility.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,10 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
225225
}
226226

227227
/// CF definition of Relation enum.
228-
pub fn relation<T, I>(&self, terms: impl Fn(&P) -> I) -> Relation<P, V>
229-
where
230-
T: AsRef<Term<V>>,
231-
I: Iterator<Item = T>,
232-
{
228+
pub fn relation(&self, mut terms: impl FnMut(&P) -> Term<V>) -> Relation<P, V> {
233229
let mut relation = Relation::Satisfied;
234230
for (package, incompat_term) in self.package_terms.iter() {
235-
match incompat_term.relation_with(terms(package)) {
231+
match incompat_term.relation_with(&terms(package)) {
236232
term::Relation::Satisfied => {}
237233
term::Relation::Contradicted => {
238234
relation = Relation::Contradicted(package.clone(), incompat_term.clone());

src/internal/memory.rs

+62-29
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pub struct Memory<P: Package, V: Version> {
2323
#[derive(Clone)]
2424
struct PackageAssignments<V: Version> {
2525
decision: Option<(V, Term<V>)>,
26-
derivations: Vec<Term<V>>,
26+
derivations_intersected: Term<V>,
27+
derivations_not_intersected_yet: Vec<Term<V>>,
2728
}
2829

2930
impl<P: Package, V: Version> Memory<P, V> {
@@ -34,12 +35,12 @@ impl<P: Package, V: Version> Memory<P, V> {
3435
}
3536
}
3637

37-
/// Retrieve terms in memory related to package.
38-
pub fn terms_for_package(&self, package: &P) -> impl Iterator<Item = &Term<V>> {
39-
self.assignments.get(package).into_iter().flat_map(|a| {
40-
let decision_iter = a.decision.iter().map(|(_, term)| term);
41-
decision_iter.chain(a.derivations.iter())
42-
})
38+
/// Retrieve intersection of terms in memory related to package.
39+
pub fn term_intersection_for_package(&mut self, package: &P) -> Term<V> {
40+
match self.assignments.get_mut(package) {
41+
None => Term::any(),
42+
Some(pa) => pa.assignment_intersection(),
43+
}
4344
}
4445

4546
/// Building step of a Memory from a given assignment.
@@ -72,7 +73,7 @@ impl<P: Package, V: Version> Memory<P, V> {
7273
.assignments
7374
.entry(package)
7475
.or_insert(PackageAssignments::new());
75-
pa.derivations.push(term);
76+
pa.derivations_not_intersected_yet.push(term);
7677
}
7778

7879
/// Extract all packages that may potentially be picked next
@@ -81,26 +82,10 @@ impl<P: Package, V: Version> Memory<P, V> {
8182
/// selected version (no "decision")
8283
/// and if it contains at least one positive derivation term
8384
/// in the partial solution.
84-
pub fn potential_packages(&self) -> impl Iterator<Item = (&P, &[Term<V>])> {
85+
pub fn potential_packages(&mut self) -> impl Iterator<Item = (&P, &Term<V>)> {
8586
self.assignments
86-
.iter()
87-
.filter_map(|(p, pa)| Self::potential_package_filter(p, pa))
88-
}
89-
90-
fn potential_package_filter<'a, 'b>(
91-
package: &'a P,
92-
package_assignments: &'b PackageAssignments<V>,
93-
) -> Option<(&'a P, &'b [Term<V>])> {
94-
if &package_assignments.decision == &None
95-
&& package_assignments
96-
.derivations
97-
.iter()
98-
.any(|t| t.is_positive())
99-
{
100-
Some((package, package_assignments.derivations.as_slice()))
101-
} else {
102-
None
103-
}
87+
.iter_mut()
88+
.filter_map(|(p, pa)| pa.potential_package_filter(p))
10489
}
10590

10691
/// If a partial solution has, for every positive derivation,
@@ -125,7 +110,8 @@ impl<V: Version> PackageAssignments<V> {
125110
fn new() -> Self {
126111
Self {
127112
decision: None,
128-
derivations: Vec::new(),
113+
derivations_intersected: Term::any(),
114+
derivations_not_intersected_yet: Vec::new(),
129115
}
130116
}
131117

@@ -134,8 +120,55 @@ impl<V: Version> PackageAssignments<V> {
134120
/// it's a total solution and version solving has succeeded.
135121
fn is_valid(&self) -> bool {
136122
match self.decision {
137-
None => self.derivations.iter().all(|t| t.is_negative()),
123+
None => {
124+
self.derivations_intersected.is_negative()
125+
&& self
126+
.derivations_not_intersected_yet
127+
.iter()
128+
.all(|t| t.is_negative())
129+
}
138130
Some(_) => true,
139131
}
140132
}
133+
134+
/// Returns intersection of all assignments (decision included).
135+
/// Mutates itself to store the intersection result.
136+
fn assignment_intersection(&mut self) -> Term<V> {
137+
self.derivation_intersection();
138+
match &self.decision {
139+
None => self.derivations_intersected.clone(),
140+
Some((_, decision_term)) => decision_term.intersection(&self.derivations_intersected),
141+
}
142+
}
143+
144+
/// Returns intersection of all derivation terms.
145+
/// Mutates itself to store the intersection result.
146+
fn derivation_intersection(&mut self) -> &Term<V> {
147+
for derivation in self.derivations_not_intersected_yet.iter() {
148+
self.derivations_intersected = self.derivations_intersected.intersection(derivation);
149+
}
150+
self.derivations_not_intersected_yet.clear();
151+
&self.derivations_intersected
152+
}
153+
154+
/// A package is a potential pick if there isn't an already
155+
/// selected version (no "decision")
156+
/// and if it contains at least one positive derivation term
157+
/// in the partial solution.
158+
fn potential_package_filter<'a, 'b, P: Package>(
159+
&'a mut self,
160+
package: &'b P,
161+
) -> Option<(&'b P, &'a Term<V>)> {
162+
if self.decision == None
163+
&& (self.derivations_intersected.is_positive()
164+
|| self
165+
.derivations_not_intersected_yet
166+
.iter()
167+
.any(|t| t.is_positive()))
168+
{
169+
Some((package, self.derivation_intersection()))
170+
} else {
171+
None
172+
}
173+
}
141174
}

src/internal/partial_solution.rs

+7-12
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
8080
.rposition(|(l, _)| *l == decision_level)
8181
.unwrap_or(self.history.len() - 1);
8282
*self = Self::from_assignments(
83-
self.history
84-
.to_owned()
83+
std::mem::take(&mut self.history)
8584
.into_iter()
8685
.take(pos + 1)
8786
.map(|(_, a)| a),
@@ -103,16 +102,12 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
103102
/// This tends to find conflicts earlier if any exist,
104103
/// since these packages will run out of versions to try more quickly.
105104
pub fn pick_package(
106-
&self,
105+
&mut self,
107106
dependency_provider: &impl DependencyProvider<P, V>,
108107
) -> Result<Option<(P, Term<V>)>, PubGrubError<P, V>> {
109108
let mut out: Option<(P, Term<V>)> = None;
110109
let mut min_key = usize::MAX;
111-
for (p, term) in self
112-
.memory
113-
.potential_packages()
114-
.map(|(p, all_terms)| (p, Term::intersect_all(all_terms.iter())))
115-
{
110+
for (p, term) in self.memory.potential_packages() {
116111
let key = dependency_provider
117112
.list_available_versions(p)
118113
.map_err(|err| PubGrubError::ErrorRetrievingVersions {
@@ -124,7 +119,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
124119
.count();
125120
if key < min_key {
126121
min_key = key;
127-
out = Some((p.clone(), term));
122+
out = Some((p.clone(), term.clone()));
128123
}
129124
}
130125
Ok(out)
@@ -167,15 +162,15 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
167162
self.memory.remove_decision(last_assignment.package());
168163
}
169164

170-
fn satisfies_any_of(&self, incompatibilities: &[Incompatibility<P, V>]) -> bool {
165+
fn satisfies_any_of(&mut self, incompatibilities: &[Incompatibility<P, V>]) -> bool {
171166
incompatibilities
172167
.iter()
173168
.any(|incompat| self.relation(incompat) == Relation::Satisfied)
174169
}
175170

176171
/// Check if the terms in the partial solution satisfy the incompatibility.
177-
pub fn relation(&self, incompat: &Incompatibility<P, V>) -> Relation<P, V> {
178-
incompat.relation(|package| self.memory.terms_for_package(package))
172+
pub fn relation(&mut self, incompat: &Incompatibility<P, V>) -> Relation<P, V> {
173+
incompat.relation(|package| self.memory.term_intersection_for_package(package))
179174
}
180175

181176
/// Find satisfier and previous satisfier decision level.

src/term.rs

+13-23
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,6 @@ impl<V: Version> Term<V> {
9292
(self.negate().intersection(&other.negate())).negate()
9393
}
9494

95-
/// Compute the intersection of multiple terms.
96-
/// Return None if the iterator is empty.
97-
pub(crate) fn intersect_all<T: AsRef<Term<V>>>(all_terms: impl Iterator<Item = T>) -> Term<V> {
98-
all_terms.fold(Self::any(), |acc, term| acc.intersection(term.as_ref()))
99-
}
100-
10195
/// Indicate if this term is a subset of another term.
10296
/// Just like for sets, we say that t1 is a subset of t2
10397
/// if and only if t1 ∩ t2 = t1.
@@ -131,8 +125,8 @@ impl<'a, V: 'a + Version> Term<V> {
131125
/// It turns out that this can also be expressed with set operations:
132126
/// S satisfies t if and only if ⋂ S ⊆ t
133127
#[cfg(test)]
134-
fn satisfied_by(&self, terms: impl Iterator<Item = &'a Term<V>>) -> bool {
135-
Self::intersect_all(terms).subset_of(self)
128+
fn satisfied_by(&self, terms_intersection: &Term<V>) -> bool {
129+
terms_intersection.subset_of(self)
136130
}
137131

138132
/// Check if a set of terms contradicts this term.
@@ -144,19 +138,15 @@ impl<'a, V: 'a + Version> Term<V> {
144138
/// S contradicts t if and only if ⋂ S is disjoint with t
145139
/// S contradicts t if and only if (⋂ S) ⋂ t = ∅
146140
#[cfg(test)]
147-
fn contradicted_by(&self, terms: impl Iterator<Item = &'a Term<V>>) -> bool {
148-
Self::intersect_all(terms).intersection(self) == Self::empty()
141+
fn contradicted_by(&self, terms_intersection: &Term<V>) -> bool {
142+
terms_intersection.intersection(self) == Self::empty()
149143
}
150144

151145
/// Check if a set of terms satisfies or contradicts a given term.
152146
/// Otherwise the relation is inconclusive.
153-
pub(crate) fn relation_with<T: AsRef<Term<V>>>(
154-
&self,
155-
other_terms: impl Iterator<Item = T>,
156-
) -> Relation {
157-
let others_intersection = Self::intersect_all(other_terms);
158-
let full_intersection = self.intersection(&others_intersection);
159-
if full_intersection == others_intersection {
147+
pub(crate) fn relation_with(&self, other_terms_intersection: &Term<V>) -> Relation {
148+
let full_intersection = self.intersection(other_terms_intersection.as_ref());
149+
if &full_intersection == other_terms_intersection.as_ref() {
160150
Relation::Satisfied
161151
} else if full_intersection == Self::empty() {
162152
Relation::Contradicted
@@ -203,13 +193,13 @@ pub mod tests {
203193
// Testing relation --------------------------------
204194

205195
#[test]
206-
fn relation_with(term in strategy(), set in prop::collection::vec(strategy(), 0..3)) {
207-
match term.relation_with(set.iter()) {
208-
Relation::Satisfied => assert!(term.satisfied_by(set.iter())),
209-
Relation::Contradicted => assert!(term.contradicted_by(set.iter())),
196+
fn relation_with(term1 in strategy(), term2 in strategy()) {
197+
match term1.relation_with(&term2) {
198+
Relation::Satisfied => assert!(term1.satisfied_by(&term2)),
199+
Relation::Contradicted => assert!(term1.contradicted_by(&term2)),
210200
Relation::Inconclusive => {
211-
assert!(!term.satisfied_by(set.iter()));
212-
assert!(!term.contradicted_by(set.iter()));
201+
assert!(!term1.satisfied_by(&term2));
202+
assert!(!term1.contradicted_by(&term2));
213203
}
214204
}
215205
}

0 commit comments

Comments
 (0)