Skip to content

Commit 30ab499

Browse files
committed
Track outdated priorities in a set
1 parent 83c0b98 commit 30ab499

File tree

2 files changed

+57
-52
lines changed

2 files changed

+57
-52
lines changed

src/internal/partial_solution.rs

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::internal::{
1717
use crate::{DependencyProvider, Package, Term, VersionSet};
1818

1919
type FnvIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
20+
type FnvIndexSet<T> = indexmap::IndexSet<T, BuildHasherDefault<FxHasher>>;
2021

2122
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
2223
pub(crate) struct DecisionLevel(pub(crate) u32);
@@ -63,8 +64,6 @@ pub struct PartialSolution<DP: DependencyProvider> {
6364
/// range.
6465
#[allow(clippy::type_complexity)]
6566
package_assignments: FnvIndexMap<Id<DP::P>, PackageAssignments<DP::P, DP::VS, DP::M>>,
66-
/// Index into `package_assignments` to decide which packages need to be re-prioritized.
67-
prioritize_decision_level: usize,
6867
/// The undecided packages order by their `Priority`.
6968
///
7069
/// The max heap allows quickly `pop`ing the highest priority package.
@@ -74,6 +73,9 @@ pub struct PartialSolution<DP: DependencyProvider> {
7473
#[allow(clippy::type_complexity)]
7574
prioritized_potential_packages:
7675
PriorityQueue<Id<DP::P>, (DP::Priority, Reverse<u32>), BuildHasherDefault<FxHasher>>,
76+
/// Packages whose derivations changed since the last time `prioritize` was called and need
77+
/// their priorities to be updated.
78+
outdated_priorities: FnvIndexSet<Id<DP::P>>,
7779
/// Whether we have never backtracked, to enable fast path optimizations.
7880
has_ever_backtracked: bool,
7981
}
@@ -181,7 +183,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
181183
current_decision_level: DecisionLevel(0),
182184
package_assignments: FnvIndexMap::default(),
183185
prioritized_potential_packages: PriorityQueue::default(),
184-
prioritize_decision_level: 0,
186+
outdated_priorities: FnvIndexSet::default(),
185187
has_ever_backtracked: false,
186188
}
187189
}
@@ -234,10 +236,6 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
234236
}
235237
},
236238
}
237-
assert_eq!(
238-
self.prioritize_decision_level,
239-
self.package_assignments.len()
240-
);
241239
}
242240
let new_idx = self.current_decision_level.0 as usize;
243241
self.current_decision_level = self.current_decision_level.increment();
@@ -288,10 +286,8 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
288286
accumulated_intersection: store[cause].get(package).unwrap().negate(),
289287
};
290288
self.next_global_index += 1;
291-
let pa_last_index = self.package_assignments.len().saturating_sub(1);
292289
match self.package_assignments.entry(package) {
293290
Entry::Occupied(mut occupied) => {
294-
let idx = occupied.index();
295291
let pa = occupied.get_mut();
296292
pa.highest_decision_level = self.current_decision_level;
297293
match &mut pa.assignments_intersection {
@@ -303,10 +299,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
303299
*t = t.intersection(&dated_derivation.accumulated_intersection);
304300
dated_derivation.accumulated_intersection = t.clone();
305301
if t.is_positive() {
306-
// we can use `swap_indices` to make `prioritize_decision_level` only go down by 1
307-
// but the copying is slower then the larger search
308-
self.prioritize_decision_level =
309-
std::cmp::min(self.prioritize_decision_level, idx);
302+
self.outdated_priorities.insert(package);
310303
}
311304
}
312305
}
@@ -315,8 +308,7 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
315308
Entry::Vacant(v) => {
316309
let term = dated_derivation.accumulated_intersection.clone();
317310
if term.is_positive() {
318-
self.prioritize_decision_level =
319-
std::cmp::min(self.prioritize_decision_level, pa_last_index);
311+
self.outdated_priorities.insert(package);
320312
}
321313
v.insert(PackageAssignments {
322314
smallest_decision_level: self.current_decision_level,
@@ -330,50 +322,49 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
330322

331323
#[cold]
332324
pub fn prioritized_packages(&self) -> impl Iterator<Item = (Id<DP::P>, &DP::VS)> {
333-
let check_all = self.prioritize_decision_level
334-
== self.current_decision_level.0.saturating_sub(1) as usize;
335325
let current_decision_level = self.current_decision_level;
336326
self.package_assignments
337-
.get_range(self.prioritize_decision_level..)
327+
.get_range(current_decision_level.0 as usize..)
338328
.unwrap()
339329
.iter()
340330
.filter(move |(_, pa)| {
341-
// We only actually need to update the package if its Been changed
342-
// since the last time we called prioritize.
343-
// Which means it's highest decision level is the current decision level,
344-
// or if we backtracked in the mean time.
345-
check_all || pa.highest_decision_level == current_decision_level
331+
// We only actually need to update the package if it was changed
332+
// since the last time we called prioritize, which means it's highest decision level
333+
// is the current decision level, or if we backtracked in the meantime.
334+
pa.highest_decision_level == current_decision_level
335+
})
336+
.filter_map(|(&p, pa)| {
337+
Some((p, pa.assignments_intersection.potential_package_filter()?))
346338
})
347-
.filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p))
348339
}
349340

350341
#[cold]
351342
pub fn pick_highest_priority_pkg(
352343
&mut self,
353344
mut prioritizer: impl FnMut(Id<DP::P>, &DP::VS) -> DP::Priority,
354345
) -> Option<Id<DP::P>> {
355-
let check_all = self.prioritize_decision_level
356-
== self.current_decision_level.0.saturating_sub(1) as usize;
357-
let current_decision_level = self.current_decision_level;
358346
let prioritized_potential_packages = &mut self.prioritized_potential_packages;
359-
self.package_assignments
360-
.get_range(self.prioritize_decision_level..)
361-
.unwrap()
362-
.iter()
363-
.filter(|(_, pa)| {
364-
// We only actually need to update the package if it has been changed
365-
// since the last time we called prioritize.
366-
// Which means it's highest decision level is the current decision level,
367-
// or if we backtracked in the meantime.
368-
check_all || pa.highest_decision_level == current_decision_level
369-
})
370-
.filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p))
371-
.for_each(|(p, r)| {
372-
let priority = prioritizer(p, r);
373-
prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32)));
374-
});
375-
self.prioritize_decision_level = self.package_assignments.len();
376-
prioritized_potential_packages.pop().map(|(p, _)| p)
347+
while let Some(p) = self.outdated_priorities.pop() {
348+
let Some(pa) = self.package_assignments.get(&p) else {
349+
continue;
350+
};
351+
let Some(r) = pa.assignments_intersection.potential_package_filter() else {
352+
continue;
353+
};
354+
let priority = prioritizer(p, r);
355+
prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32)));
356+
}
357+
while let Some(p) = self.prioritized_potential_packages.pop().map(|(p, _)| p) {
358+
if self
359+
.package_assignments
360+
.get(&p)
361+
.and_then(|pa| pa.assignments_intersection.potential_package_filter())
362+
.is_some()
363+
{
364+
return Some(p);
365+
}
366+
}
367+
None
377368
}
378369

379370
/// If a partial solution has, for every positive derivation,
@@ -413,12 +404,20 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
413404
/// Backtrack the partial solution to a given decision level.
414405
pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) {
415406
self.current_decision_level = decision_level;
416-
self.package_assignments.retain(|_, pa| {
407+
self.package_assignments.retain(|p, pa| {
417408
if pa.smallest_decision_level > decision_level {
418409
// Remove all entries that have a smallest decision level higher than the backtrack target.
419410
false
420411
} else if pa.highest_decision_level <= decision_level {
421412
// Do not change entries older than the backtrack decision level target.
413+
if pa
414+
.assignments_intersection
415+
.potential_package_filter()
416+
.is_some()
417+
&& self.prioritized_potential_packages.get(p).is_none()
418+
{
419+
self.outdated_priorities.insert(*p);
420+
}
422421
true
423422
} else {
424423
// smallest_decision_level <= decision_level < highest_decision_level
@@ -443,12 +442,15 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
443442
// Reset the assignments intersection.
444443
pa.assignments_intersection =
445444
AssignmentsIntersection::Derivations(last.accumulated_intersection.clone());
445+
446+
self.prioritized_potential_packages.remove(p);
447+
if pa.assignments_intersection.term().is_positive() {
448+
self.outdated_priorities.insert(*p);
449+
}
446450
true
447451
}
448452
});
449-
// Throw away all stored priority levels, And mark that they all need to be recomputed.
450-
self.prioritized_potential_packages.clear();
451-
self.prioritize_decision_level = self.current_decision_level.0.saturating_sub(1) as usize;
453+
// Throw away all stored priority levels and mark them for recomputing
452454
self.has_ever_backtracked = true;
453455
}
454456

@@ -714,12 +716,12 @@ impl<VS: VersionSet> AssignmentsIntersection<VS> {
714716
/// selected version (no "decision")
715717
/// and if it contains at least one positive derivation term
716718
/// in the partial solution.
717-
fn potential_package_filter<P: Package>(&self, package: Id<P>) -> Option<(Id<P>, &VS)> {
719+
fn potential_package_filter(&self) -> Option<&VS> {
718720
match self {
719721
Self::Decision { .. } => None,
720722
Self::Derivations(term_intersection) => {
721723
if term_intersection.is_positive() {
722-
Some((package, term_intersection.unwrap_positive()))
724+
Some(term_intersection.unwrap_positive())
723725
} else {
724726
None
725727
}

src/solver.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ pub fn resolve<DP: DependencyProvider>(
169169
.partial_solution
170170
.term_intersection_for_package(next)
171171
.ok_or_else(|| {
172-
PubGrubError::Failure("a package was chosen but we don't have a term.".into())
172+
PubGrubError::Failure(format!(
173+
"package {} was chosen but we don't have a term.",
174+
next.into_raw()
175+
))
173176
})?;
174177
let decision = dependency_provider
175178
.choose_version(

0 commit comments

Comments
 (0)