Skip to content

Commit 4a48371

Browse files
authored
perf: fewer intersections in satisfier (pubgrub-rs#174)
* check that nothing changed * inline subset_of * Apply !incompat_term to both sides * T.intersection( !T ) == empty * precomputed start_term.intersection(&incompat_term.negate()) * move the checking code * switch to partition_point * remove the option * compute intersection_term outside of satisfier * intersection with any is self * remove test code * remove unused arguments * rename vars * dont clone P * nothing meaningful * use cause not index * clippy
1 parent be8c559 commit 4a48371

File tree

3 files changed

+49
-60
lines changed

3 files changed

+49
-60
lines changed

src/internal/core.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ impl<P: Package, VS: VersionSet, Priority: Ord + Clone> State<P, VS, Priority> {
194194
DifferentDecisionLevels {
195195
previous_satisfier_level,
196196
} => {
197+
let package = package.clone();
197198
self.backtrack(
198199
current_incompat_id,
199200
current_incompat_changed,
@@ -206,7 +207,7 @@ impl<P: Package, VS: VersionSet, Priority: Ord + Clone> State<P, VS, Priority> {
206207
let prior_cause = Incompatibility::prior_cause(
207208
current_incompat_id,
208209
satisfier_cause,
209-
&package,
210+
package,
210211
&self.incompatibility_store,
211212
);
212213
log::info!("prior cause: {}", prior_cause);

src/internal/partial_solution.rs

Lines changed: 46 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ pub enum SatisfierSearch<P: Package, VS: VersionSet> {
143143
},
144144
}
145145

146+
type SatisfiedMap<'i, P, VS> = SmallMap<&'i P, (Option<IncompId<P, VS>>, u32, DecisionLevel)>;
147+
146148
impl<P: Package, VS: VersionSet, Priority: Ord + Clone> PartialSolution<P, VS, Priority> {
147149
/// Initialize an empty PartialSolution.
148150
pub fn empty() -> Self {
@@ -390,37 +392,33 @@ impl<P: Package, VS: VersionSet, Priority: Ord + Clone> PartialSolution<P, VS, P
390392
}
391393

392394
/// Figure out if the satisfier and previous satisfier are of different decision levels.
393-
pub fn satisfier_search(
395+
pub fn satisfier_search<'i>(
394396
&self,
395-
incompat: &Incompatibility<P, VS>,
397+
incompat: &'i Incompatibility<P, VS>,
396398
store: &Arena<Incompatibility<P, VS>>,
397-
) -> (P, SatisfierSearch<P, VS>) {
399+
) -> (&'i P, SatisfierSearch<P, VS>) {
398400
let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments);
399-
let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map
401+
let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map
400402
.iter()
401403
.max_by_key(|(_p, (_, global_index, _))| global_index)
402404
.unwrap();
403-
let satisfier_package = satisfier_package.clone();
404405
let previous_satisfier_level = Self::find_previous_satisfier(
405406
incompat,
406-
&satisfier_package,
407+
satisfier_package,
407408
satisfied_map,
408409
&self.package_assignments,
409410
store,
410411
);
411-
if previous_satisfier_level < satisfier_decision_level {
412-
let search_result = SatisfierSearch::DifferentDecisionLevels {
413-
previous_satisfier_level,
414-
};
415-
(satisfier_package, search_result)
412+
let search_result = if previous_satisfier_level >= satisfier_decision_level {
413+
SatisfierSearch::SameDecisionLevels {
414+
satisfier_cause: satisfier_cause.unwrap(),
415+
}
416416
} else {
417-
let satisfier_pa = self.package_assignments.get(&satisfier_package).unwrap();
418-
let dd = &satisfier_pa.dated_derivations[satisfier_index];
419-
let search_result = SatisfierSearch::SameDecisionLevels {
420-
satisfier_cause: dd.cause,
421-
};
422-
(satisfier_package, search_result)
423-
}
417+
SatisfierSearch::DifferentDecisionLevels {
418+
previous_satisfier_level,
419+
}
420+
};
421+
(satisfier_package, search_result)
424422
}
425423

426424
/// A satisfier is the earliest assignment in partial solution such that the incompatibility
@@ -432,52 +430,51 @@ impl<P: Package, VS: VersionSet, Priority: Ord + Clone> PartialSolution<P, VS, P
432430
/// Question: This is possible since we added a "global_index" to every dated_derivation.
433431
/// It would be nice if we could get rid of it, but I don't know if then it will be possible
434432
/// to return a coherent previous_satisfier_level.
435-
fn find_satisfier(
436-
incompat: &Incompatibility<P, VS>,
433+
fn find_satisfier<'i>(
434+
incompat: &'i Incompatibility<P, VS>,
437435
package_assignments: &FnvIndexMap<P, PackageAssignments<P, VS>>,
438-
) -> SmallMap<P, (usize, u32, DecisionLevel)> {
436+
) -> SatisfiedMap<'i, P, VS> {
439437
let mut satisfied = SmallMap::Empty;
440438
for (package, incompat_term) in incompat.iter() {
441439
let pa = package_assignments.get(package).expect("Must exist");
442-
satisfied.insert(
443-
package.clone(),
444-
pa.satisfier(package, incompat_term, &Term::any()),
445-
);
440+
satisfied.insert(package, pa.satisfier(package, &incompat_term.negate()));
446441
}
447442
satisfied
448443
}
449444

450445
/// Earliest assignment in the partial solution before satisfier
451446
/// such that incompatibility is satisfied by the partial solution up to
452447
/// and including that assignment plus satisfier.
453-
fn find_previous_satisfier(
448+
fn find_previous_satisfier<'i>(
454449
incompat: &Incompatibility<P, VS>,
455-
satisfier_package: &P,
456-
mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>,
450+
satisfier_package: &'i P,
451+
mut satisfied_map: SatisfiedMap<'i, P, VS>,
457452
package_assignments: &FnvIndexMap<P, PackageAssignments<P, VS>>,
458453
store: &Arena<Incompatibility<P, VS>>,
459454
) -> DecisionLevel {
460455
// First, let's retrieve the previous derivations and the initial accum_term.
461456
let satisfier_pa = package_assignments.get(satisfier_package).unwrap();
462-
let (satisfier_index, _gidx, _dl) = satisfied_map.get_mut(satisfier_package).unwrap();
457+
let (satisfier_cause, _gidx, _dl) = satisfied_map.get(&satisfier_package).unwrap();
463458

464-
let accum_term = if *satisfier_index == satisfier_pa.dated_derivations.len() {
459+
let accum_term = if let &Some(cause) = satisfier_cause {
460+
store[cause].get(satisfier_package).unwrap().negate()
461+
} else {
465462
match &satisfier_pa.assignments_intersection {
466463
AssignmentsIntersection::Derivations(_) => panic!("must be a decision"),
467464
AssignmentsIntersection::Decision((_, _, term)) => term.clone(),
468465
}
469-
} else {
470-
let dd = &satisfier_pa.dated_derivations[*satisfier_index];
471-
store[dd.cause].get(satisfier_package).unwrap().negate()
472466
};
473467

474468
let incompat_term = incompat
475469
.get(satisfier_package)
476470
.expect("satisfier package not in incompat");
477471

478472
satisfied_map.insert(
479-
satisfier_package.clone(),
480-
satisfier_pa.satisfier(satisfier_package, incompat_term, &accum_term),
473+
satisfier_package,
474+
satisfier_pa.satisfier(
475+
satisfier_package,
476+
&accum_term.intersection(&incompat_term.negate()),
477+
),
481478
);
482479

483480
// Finally, let's identify the decision level of that previous satisfier.
@@ -497,44 +494,34 @@ impl<P: Package, VS: VersionSet> PackageAssignments<P, VS> {
497494
fn satisfier(
498495
&self,
499496
package: &P,
500-
incompat_term: &Term<VS>,
501497
start_term: &Term<VS>,
502-
) -> (usize, u32, DecisionLevel) {
498+
) -> (Option<IncompId<P, VS>>, u32, DecisionLevel) {
499+
let empty = Term::empty();
503500
// Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision.
504-
for (idx, dated_derivation) in self.dated_derivations.iter().enumerate() {
505-
if dated_derivation
506-
.accumulated_intersection
507-
.intersection(start_term)
508-
.subset_of(incompat_term)
509-
{
510-
// We found the derivation causing satisfaction.
511-
return (
512-
idx,
513-
dated_derivation.global_index,
514-
dated_derivation.decision_level,
515-
);
516-
}
501+
let idx = self
502+
.dated_derivations
503+
.as_slice()
504+
.partition_point(|dd| dd.accumulated_intersection.intersection(start_term) != empty);
505+
if let Some(dd) = self.dated_derivations.get(idx) {
506+
debug_assert_eq!(dd.accumulated_intersection.intersection(start_term), empty);
507+
return (Some(dd.cause), dd.global_index, dd.decision_level);
517508
}
518509
// If it wasn't found in the derivations,
519510
// it must be the decision which is last (if called in the right context).
520511
match &self.assignments_intersection {
521-
AssignmentsIntersection::Decision((global_index, _, _)) => (
522-
self.dated_derivations.len(),
523-
*global_index,
524-
self.highest_decision_level,
525-
),
512+
AssignmentsIntersection::Decision((global_index, _, _)) => {
513+
(None, *global_index, self.highest_decision_level)
514+
}
526515
AssignmentsIntersection::Derivations(accumulated_intersection) => {
527516
unreachable!(
528517
concat!(
529518
"while processing package {}: ",
530-
"accum_term = {} isn't a subset of incompat_term = {}, ",
519+
"accum_term = {} has overlap with incompat_term = {}, ",
531520
"which means the last assignment should have been a decision, ",
532521
"but instead it was a derivation. This shouldn't be possible! ",
533522
"(Maybe your Version ordering is broken?)"
534523
),
535-
package,
536-
accumulated_intersection.intersection(start_term),
537-
incompat_term
524+
package, accumulated_intersection, start_term
538525
)
539526
}
540527
}

src/term.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ impl<VS: VersionSet> Term<VS> {
107107
/// Indicate if this term is a subset of another term.
108108
/// Just like for sets, we say that t1 is a subset of t2
109109
/// if and only if t1 ∩ t2 = t1.
110+
#[cfg(test)]
110111
pub(crate) fn subset_of(&self, other: &Self) -> bool {
111112
self == &self.intersection(other)
112113
}

0 commit comments

Comments
 (0)