Skip to content

Commit 110c17d

Browse files
committed
Create an arena for package names
1 parent ff09b46 commit 110c17d

File tree

7 files changed

+228
-205
lines changed

7 files changed

+228
-205
lines changed

benches/large_case.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use std::time::Duration;
44
extern crate criterion;
55
use self::criterion::*;
66

7-
use pubgrub::package::Package;
8-
use pubgrub::range::Range;
9-
use pubgrub::solver::{resolve, OfflineDependencyProvider};
10-
use pubgrub::version::SemanticVersion;
11-
use pubgrub::version_set::VersionSet;
7+
use pubgrub::Package;
8+
use pubgrub::Range;
9+
use pubgrub::SemanticVersion;
10+
use pubgrub::VersionSet;
11+
use pubgrub::{resolve, OfflineDependencyProvider};
1212
use serde::de::Deserialize;
1313

1414
fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>(

src/internal/arena.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::fmt;
2-
use std::hash::{Hash, Hasher};
2+
use std::hash::{BuildHasherDefault, Hash, Hasher};
33
use std::marker::PhantomData;
44
use std::ops::{Index, Range};
55

6+
type FnvIndexSet<K> = indexmap::IndexSet<K, BuildHasherDefault<rustc_hash::FxHasher>>;
7+
68
/// The index of a value allocated in an arena that holds `T`s.
79
///
810
/// The Clone, Copy and other traits are defined manually because
@@ -124,3 +126,44 @@ impl<T> Index<Range<Id<T>>> for Arena<T> {
124126
&self.data[(id.start.raw as usize)..(id.end.raw as usize)]
125127
}
126128
}
129+
130+
/// Yet another index-based arena. This one de-duplicates entries by hashing.
131+
///
132+
/// An arena is a kind of simple grow-only allocator, backed by a `Vec`
133+
/// where all items have the same lifetime, making it easier
134+
/// to have references between those items.
135+
/// In this case the `Vec` is inside a `IndexSet` allowing fast lookup by value not just index.
136+
/// They are all dropped at once when the arena is dropped.
137+
#[derive(Clone, PartialEq, Eq)]
138+
pub struct HashArena<T: Hash + Eq> {
139+
data: FnvIndexSet<T>,
140+
}
141+
142+
impl<T: Hash + Eq + fmt::Debug> fmt::Debug for HashArena<T> {
143+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
144+
fmt.debug_struct("Arena")
145+
.field("len", &self.data.len())
146+
.field("data", &self.data)
147+
.finish()
148+
}
149+
}
150+
151+
impl<T: Hash + Eq> HashArena<T> {
152+
pub fn new() -> Self {
153+
HashArena {
154+
data: FnvIndexSet::default(),
155+
}
156+
}
157+
158+
pub fn alloc(&mut self, value: T) -> Id<T> {
159+
let (raw, _) = self.data.insert_full(value);
160+
Id::from(raw as u32)
161+
}
162+
}
163+
164+
impl<T: Hash + Eq> Index<Id<T>> for HashArena<T> {
165+
type Output = T;
166+
fn index(&self, id: Id<T>) -> &T {
167+
&self.data[id.raw as usize]
168+
}
169+
}

src/internal/core.rs

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@ use crate::solver::DependencyProvider;
1919
use crate::type_aliases::{IncompDpId, Map};
2020
use crate::version_set::VersionSet;
2121

22+
use super::arena::{HashArena, Id};
23+
2224
/// Current state of the PubGrub algorithm.
2325
#[derive(Clone)]
2426
pub struct State<DP: DependencyProvider> {
25-
root_package: DP::P,
27+
pub root_package: Id<DP::P>,
2628
root_version: DP::V,
2729

2830
#[allow(clippy::type_complexity)]
29-
incompatibilities: Map<DP::P, Vec<IncompDpId<DP>>>,
31+
incompatibilities: Map<Id<DP::P>, Vec<IncompDpId<DP>>>,
3032

3133
/// Store the ids of incompatibilities that are already contradicted.
3234
/// For each one keep track of the decision level when it was found to be contradicted.
@@ -36,7 +38,7 @@ pub struct State<DP: DependencyProvider> {
3638
/// All incompatibilities expressing dependencies,
3739
/// with common dependents merged.
3840
#[allow(clippy::type_complexity)]
39-
merged_dependencies: Map<(DP::P, DP::P), SmallVec<IncompDpId<DP>>>,
41+
merged_dependencies: Map<(Id<DP::P>, Id<DP::P>), SmallVec<IncompDpId<DP>>>,
4042

4143
/// Partial solution.
4244
/// TODO: remove pub.
@@ -45,29 +47,35 @@ pub struct State<DP: DependencyProvider> {
4547
/// The store is the reference storage for all incompatibilities.
4648
pub incompatibility_store: Arena<Incompatibility<DP::P, DP::VS, DP::M>>,
4749

50+
/// The store is the reference storage for all incompatibilities.
51+
pub package_store: HashArena<DP::P>,
52+
4853
/// This is a stack of work to be done in `unit_propagation`.
4954
/// It can definitely be a local variable to that method, but
5055
/// this way we can reuse the same allocation for better performance.
51-
unit_propagation_buffer: SmallVec<DP::P>,
56+
unit_propagation_buffer: SmallVec<Id<DP::P>>,
5257
}
5358

5459
impl<DP: DependencyProvider> State<DP> {
5560
/// Initialization of PubGrub state.
5661
pub fn init(root_package: DP::P, root_version: DP::V) -> Self {
5762
let mut incompatibility_store = Arena::new();
63+
let mut package_store = HashArena::new();
64+
let root_package = package_store.alloc(root_package);
5865
let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
59-
root_package.clone(),
66+
root_package,
6067
root_version.clone(),
6168
));
6269
let mut incompatibilities = Map::default();
63-
incompatibilities.insert(root_package.clone(), vec![not_root_id]);
70+
incompatibilities.insert(root_package, vec![not_root_id]);
6471
Self {
6572
root_package,
6673
root_version,
6774
incompatibilities,
6875
contradicted_incompatibilities: Map::default(),
6976
partial_solution: PartialSolution::empty(),
7077
incompatibility_store,
78+
package_store,
7179
unit_propagation_buffer: SmallVec::Empty,
7280
merged_dependencies: Map::default(),
7381
}
@@ -82,18 +90,19 @@ impl<DP: DependencyProvider> State<DP> {
8290
/// Add an incompatibility to the state.
8391
pub fn add_incompatibility_from_dependencies(
8492
&mut self,
85-
package: DP::P,
93+
package: Id<DP::P>,
8694
version: DP::V,
8795
deps: impl IntoIterator<Item = (DP::P, DP::VS)>,
8896
) -> std::ops::Range<IncompDpId<DP>> {
8997
// Create incompatibilities and allocate them in the store.
9098
let new_incompats_id_range =
9199
self.incompatibility_store
92-
.alloc_iter(deps.into_iter().map(|dep| {
100+
.alloc_iter(deps.into_iter().map(|(dep_p, dep_vs)| {
101+
let dep_pid = self.package_store.alloc(dep_p);
93102
Incompatibility::from_dependency(
94-
package.clone(),
103+
package,
95104
<DP::VS as VersionSet>::singleton(version.clone()),
96-
dep,
105+
(dep_pid, dep_vs),
97106
)
98107
}));
99108
// Merge the newly created incompatibilities with the older ones.
@@ -105,7 +114,7 @@ impl<DP: DependencyProvider> State<DP> {
105114

106115
/// Unit propagation is the core mechanism of the solving algorithm.
107116
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
108-
pub fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError<DP>> {
117+
pub fn unit_propagation(&mut self, package: Id<DP::P>) -> Result<(), NoSolutionError<DP>> {
109118
self.unit_propagation_buffer.clear();
110119
self.unit_propagation_buffer.push(package);
111120
while let Some(current_package) = self.unit_propagation_buffer.pop() {
@@ -126,7 +135,7 @@ impl<DP: DependencyProvider> State<DP> {
126135
// we must perform conflict resolution.
127136
Relation::Satisfied => {
128137
log::info!(
129-
"Start conflict resolution because incompat satisfied:\n {}",
138+
"Start conflict resolution because incompat satisfied:\n {:?}",
130139
current_incompat
131140
);
132141
conflict_id = Some(incompat_id);
@@ -139,7 +148,7 @@ impl<DP: DependencyProvider> State<DP> {
139148
// In practice the most common pathology is adding the same package repeatedly.
140149
// So we only check if it is duplicated with the last item.
141150
if self.unit_propagation_buffer.last() != Some(&package_almost) {
142-
self.unit_propagation_buffer.push(package_almost.clone());
151+
self.unit_propagation_buffer.push(package_almost);
143152
}
144153
// Add (not term) to the partial solution with incompat as cause.
145154
self.partial_solution.add_derivation(
@@ -165,7 +174,7 @@ impl<DP: DependencyProvider> State<DP> {
165174
self.build_derivation_tree(terminal_incompat_id)
166175
})?;
167176
self.unit_propagation_buffer.clear();
168-
self.unit_propagation_buffer.push(package_almost.clone());
177+
self.unit_propagation_buffer.push(package_almost);
169178
// Add to the partial solution with incompat as cause.
170179
self.partial_solution.add_derivation(
171180
package_almost,
@@ -188,12 +197,12 @@ impl<DP: DependencyProvider> State<DP> {
188197
fn conflict_resolution(
189198
&mut self,
190199
incompatibility: IncompDpId<DP>,
191-
) -> Result<(DP::P, IncompDpId<DP>), IncompDpId<DP>> {
200+
) -> Result<(Id<DP::P>, IncompDpId<DP>), IncompDpId<DP>> {
192201
let mut current_incompat_id = incompatibility;
193202
let mut current_incompat_changed = false;
194203
loop {
195204
if self.incompatibility_store[current_incompat_id]
196-
.is_terminal(&self.root_package, &self.root_version)
205+
.is_terminal(self.root_package, &self.root_version)
197206
{
198207
return Err(current_incompat_id);
199208
} else {
@@ -205,7 +214,6 @@ impl<DP: DependencyProvider> State<DP> {
205214
DifferentDecisionLevels {
206215
previous_satisfier_level,
207216
} => {
208-
let package = package.clone();
209217
self.backtrack(
210218
current_incompat_id,
211219
current_incompat_changed,
@@ -221,7 +229,7 @@ impl<DP: DependencyProvider> State<DP> {
221229
package,
222230
&self.incompatibility_store,
223231
);
224-
log::info!("prior cause: {}", prior_cause);
232+
log::info!("prior cause: {:?}", prior_cause);
225233
current_incompat_id = self.incompatibility_store.alloc(prior_cause);
226234
current_incompat_changed = true;
227235
}
@@ -264,19 +272,16 @@ impl<DP: DependencyProvider> State<DP> {
264272
fn merge_incompatibility(&mut self, mut id: IncompDpId<DP>) {
265273
if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() {
266274
// If we are a dependency, there's a good chance we can be merged with a previous dependency
267-
let deps_lookup = self
268-
.merged_dependencies
269-
.entry((p1.clone(), p2.clone()))
270-
.or_default();
275+
let deps_lookup = self.merged_dependencies.entry((p1, p2)).or_default();
271276
if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| {
272277
self.incompatibility_store[id]
273278
.merge_dependents(&self.incompatibility_store[*past])
274279
.map(|m| (past, m))
275280
}) {
276281
let new = self.incompatibility_store.alloc(merged);
277-
for (pkg, _) in self.incompatibility_store[new].iter() {
282+
for (&pkg, _) in self.incompatibility_store[new].iter() {
278283
self.incompatibilities
279-
.entry(pkg.clone())
284+
.entry(pkg)
280285
.or_default()
281286
.retain(|id| id != past);
282287
}
@@ -286,14 +291,11 @@ impl<DP: DependencyProvider> State<DP> {
286291
deps_lookup.push(id);
287292
}
288293
}
289-
for (pkg, term) in self.incompatibility_store[id].iter() {
294+
for (&pkg, term) in self.incompatibility_store[id].iter() {
290295
if cfg!(debug_assertions) {
291296
assert_ne!(term, &crate::term::Term::any());
292297
}
293-
self.incompatibilities
294-
.entry(pkg.clone())
295-
.or_default()
296-
.push(id);
298+
self.incompatibilities.entry(pkg).or_default().push(id);
297299
}
298300
}
299301

@@ -328,6 +330,7 @@ impl<DP: DependencyProvider> State<DP> {
328330
id,
329331
&shared_ids,
330332
&self.incompatibility_store,
333+
&self.package_store,
331334
&precomputed,
332335
);
333336
precomputed.insert(id, Arc::new(tree));

0 commit comments

Comments
 (0)