Skip to content

Commit 717289b

Browse files
committed
release: version 0.2.1
2 parents 0a5a94c + 57027b3 commit 717289b

21 files changed

+1315
-778
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Runs `cargo publish --dry-run` before another release
2+
3+
name: Check crate publishing works
4+
on:
5+
pull_request:
6+
branches: [ release ]
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
11+
jobs:
12+
cargo_publish_dry_run:
13+
name: Publishing works
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
- name: Install stable Rust
18+
uses: actions-rs/toolchain@v1
19+
with:
20+
toolchain: stable
21+
profile: minimal
22+
23+
- name: Get Cargo version
24+
id: cargo_version
25+
run: echo "::set-output name=version::$(cargo -V | tr -d ' ')"
26+
shell: bash
27+
28+
- name: Download cache
29+
uses: actions/cache@v2
30+
with:
31+
path: |
32+
~/.cargo/registry/index/
33+
~/.cargo/registry/cache/
34+
~/.cargo/git/db/
35+
target/
36+
key: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
37+
restore-keys: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}
38+
39+
- name: Run `cargo publish --dry-run`
40+
run: cargo publish --dry-run

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: CI
22
on:
33
pull_request:
44
push:
5-
branches: [ master, dev ]
5+
branches: [ release, dev ]
66
schedule: [ cron: "0 6 * * 4" ]
77

88
env:

CHANGELOG.md

+38-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,37 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## Unreleased [(diff)][diff-unreleased]
5+
## Unreleased [(diff)][unreleased-diff]
66

7-
## [0.2.0] - 2020-11-19 - [(diff with 0.1.0)][diff-0.2.0]
7+
## [0.2.1] - 2021-06-30 - [(diff with 0.2.0)][0.2.0-diff]
8+
9+
This release is focused on performance improvements and code readability, without any change to the public API.
10+
11+
The code tends to be simpler around tricky parts of the algorithm such as conflict resolution.
12+
Some data structures have been rewritten (with no unsafe) to lower memory usage.
13+
Depending on scenarios, version 0.2.1 is 3 to 8 times faster than 0.2.0.
14+
As an example, solving all elm package versions existing went from 580ms to 175ms on my laptop.
15+
While solving a specific subset of packages from crates.io went from 2.5s to 320ms on my laptop.
16+
17+
Below are listed all the important changes in the internal parts of the API.
18+
19+
#### Added
20+
21+
- New `SmallVec` data structure (with no unsafe) using fixed size arrays for up to 2 entries.
22+
- New `SmallMap` data structure (with no unsafe) using fixed size arrays for up to 2 entries.
23+
- New `Arena` data structure (with no unsafe) backed by a `Vec` and indexed with `Id<T>` where `T` is phantom data.
24+
25+
#### Changed
26+
27+
- Updated the `large_case` benchmark to run with both u16 and string package identifiers in registries.
28+
- Use the new `Arena` for the incompatibility store, and use its `Id<T>` identifiers to reference incompatibilities instead of full owned copies in the `incompatibilities` field of the solver `State`.
29+
- Save satisfier indices of each package involved in an incompatibility when looking for its satisfier. This speeds up the search for the previous satisfier.
30+
- Early unit propagation loop restart at the first conflict found instead of continuing evaluation for the current package.
31+
- Index incompatibilities by package in a hash map instead of using a vec.
32+
- Keep track of already contradicted incompatibilities in a `Set` until the next backtrack to speed up unit propagation.
33+
- Unify `history` and `memory` in `partial_solution` under a unique hash map indexed by packages. This should speed up access to relevan terms in conflict resolution.
34+
35+
## [0.2.0] - 2020-11-19 - [(diff with 0.1.0)][0.1.0-diff]
836

937
This release brings many important improvements to PubGrub.
1038
The gist of it is:
@@ -78,7 +106,9 @@ The gist of it is:
78106

79107
#### Changed
80108

81-
- CI workflow was improved (`./github/workflows/`), including a check for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and [Clippy ](https://github.com/rust-lang/rust-clippy) for source code linting.
109+
- CI workflow was improved (`./github/workflows/`), including a check for
110+
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and
111+
[Clippy](https://github.com/rust-lang/rust-clippy) for source code linting.
82112
- Using SPDX license identifiers instead of MPL-2.0 classic file headers.
83113
- `State.incompatibilities` is now wrapped inside a `Rc`.
84114
- `DecisionLevel(u32)` is used in place of `usize` for partial solution decision levels.
@@ -131,7 +161,10 @@ The gist of it is:
131161
- `.gitignore` configured for a Rust project.
132162
- `.github/workflows/` CI to automatically build, test and document on push and pull requests.
133163

164+
[0.2.1]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.1
134165
[0.2.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.0
135166
[0.1.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.1.0
136-
[diff-unreleased]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev
137-
[diff-0.2.0]: https://github.com/mpizenberg/elm-pointer-events/compare/v0.1.0...v0.2.0
167+
168+
[unreleased-diff]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev
169+
[0.2.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.2.0...v0.2.1
170+
[0.1.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.1.0...v0.2.0

Cargo.toml

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
[package]
44
name = "pubgrub"
5-
version = "0.2.0"
6-
authors = ["Matthieu Pizenberg <[email protected]>", "Alex Tokarev <[email protected]>", "Jacob Finkelman <[email protected]>"]
5+
version = "0.2.1"
6+
authors = [
7+
"Matthieu Pizenberg <[email protected]>",
8+
"Alex Tokarev <[email protected]>",
9+
"Jacob Finkelman <[email protected]>",
10+
]
711
edition = "2018"
812
description = "PubGrub version solving algorithm"
913
readme = "README.md"
10-
repository = "https://github.com/mpizenberg/pubgrub-rs"
14+
repository = "https://github.com/pubgrub-rs/pubgrub"
1115
license = "MPL-2.0"
1216
keywords = ["dependency", "pubgrub", "semver", "solver", "version"]
1317
categories = ["algorithms"]
@@ -22,8 +26,8 @@ serde = { version = "1.0", features = ["derive"], optional = true }
2226

2327
[dev-dependencies]
2428
proptest = "0.10.1"
25-
ron="0.6"
26-
varisat="0.2.2"
29+
ron = "0.6"
30+
varisat = "0.2.2"
2731
criterion = "0.3"
2832

2933
[[bench]]

benches/large_case.rs

+30-16
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,44 @@ use std::time::Duration;
44
extern crate criterion;
55
use self::criterion::*;
66

7+
use pubgrub::package::Package;
78
use pubgrub::solver::{resolve, OfflineDependencyProvider};
8-
use pubgrub::version::NumberVersion;
9+
use pubgrub::version::{NumberVersion, SemanticVersion, Version};
10+
use serde::de::Deserialize;
11+
use std::hash::Hash;
12+
13+
fn bench<'a, P: Package + Deserialize<'a>, V: Version + Hash + Deserialize<'a>>(
14+
b: &mut Bencher,
15+
case: &'a str,
16+
) {
17+
let dependency_provider: OfflineDependencyProvider<P, V> = ron::de::from_str(&case).unwrap();
18+
19+
b.iter(|| {
20+
for p in dependency_provider.packages() {
21+
for n in dependency_provider.versions(p).unwrap() {
22+
let _ = resolve(&dependency_provider, p.clone(), n.clone());
23+
}
24+
}
25+
});
26+
}
927

1028
fn bench_nested(c: &mut Criterion) {
1129
let mut group = c.benchmark_group("large_cases");
1230
group.measurement_time(Duration::from_secs(20));
1331

1432
for case in std::fs::read_dir("test-examples").unwrap() {
1533
let case = case.unwrap().path();
16-
17-
group.bench_function(
18-
format!("{}", case.file_name().unwrap().to_string_lossy()),
19-
|b| {
20-
let s = std::fs::read_to_string(&case).unwrap();
21-
let dependency_provider: OfflineDependencyProvider<u16, NumberVersion> =
22-
ron::de::from_str(&s).unwrap();
23-
24-
b.iter(|| {
25-
for &n in dependency_provider.versions(&0).unwrap() {
26-
let _ = resolve(&dependency_provider, 0, n);
27-
}
28-
});
29-
},
30-
);
34+
let name = case.file_name().unwrap().to_string_lossy();
35+
let data = std::fs::read_to_string(&case).unwrap();
36+
if name.ends_with("u16_NumberVersion.ron") {
37+
group.bench_function(name, |b| {
38+
bench::<u16, NumberVersion>(b, &data);
39+
});
40+
} else if name.ends_with("str_SemanticVersion.ron") {
41+
group.bench_function(name, |b| {
42+
bench::<&str, SemanticVersion>(b, &data);
43+
});
44+
}
3145
}
3246

3347
group.finish();

src/internal/arena.rs

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use std::{
2+
fmt,
3+
hash::{Hash, Hasher},
4+
marker::PhantomData,
5+
ops::{Index, Range},
6+
};
7+
8+
/// The index of a value allocated in an arena that holds `T`s.
9+
///
10+
/// The Clone, Copy and other traits are defined manually because
11+
/// deriving them adds some additional constraints on the `T` generic type
12+
/// that we actually don't need since it is phantom.
13+
///
14+
/// <https://github.com/rust-lang/rust/issues/26925>
15+
pub struct Id<T> {
16+
raw: u32,
17+
_ty: PhantomData<fn() -> T>,
18+
}
19+
20+
impl<T> Clone for Id<T> {
21+
fn clone(&self) -> Self {
22+
*self
23+
}
24+
}
25+
26+
impl<T> Copy for Id<T> {}
27+
28+
impl<T> PartialEq for Id<T> {
29+
fn eq(&self, other: &Id<T>) -> bool {
30+
self.raw == other.raw
31+
}
32+
}
33+
34+
impl<T> Eq for Id<T> {}
35+
36+
impl<T> Hash for Id<T> {
37+
fn hash<H: Hasher>(&self, state: &mut H) {
38+
self.raw.hash(state)
39+
}
40+
}
41+
42+
impl<T> fmt::Debug for Id<T> {
43+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44+
let mut type_name = std::any::type_name::<T>();
45+
if let Some(id) = type_name.rfind(':') {
46+
type_name = &type_name[id + 1..]
47+
}
48+
write!(f, "Id::<{}>({})", type_name, self.raw)
49+
}
50+
}
51+
52+
impl<T> Id<T> {
53+
pub fn into_raw(self) -> usize {
54+
self.raw as usize
55+
}
56+
fn from(n: u32) -> Self {
57+
Self {
58+
raw: n as u32,
59+
_ty: PhantomData,
60+
}
61+
}
62+
pub fn range_to_iter(range: Range<Self>) -> impl Iterator<Item = Self> {
63+
let start = range.start.raw;
64+
let end = range.end.raw;
65+
(start..end).map(Self::from)
66+
}
67+
}
68+
69+
/// Yet another index-based arena.
70+
///
71+
/// An arena is a kind of simple grow-only allocator, backed by a `Vec`
72+
/// where all items have the same lifetime, making it easier
73+
/// to have references between those items.
74+
/// They are all dropped at once when the arena is dropped.
75+
#[derive(Clone, PartialEq, Eq)]
76+
pub struct Arena<T> {
77+
data: Vec<T>,
78+
}
79+
80+
impl<T: fmt::Debug> fmt::Debug for Arena<T> {
81+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
82+
fmt.debug_struct("Arena")
83+
.field("len", &self.data.len())
84+
.field("data", &self.data)
85+
.finish()
86+
}
87+
}
88+
89+
impl<T> Arena<T> {
90+
pub fn new() -> Arena<T> {
91+
Arena { data: Vec::new() }
92+
}
93+
94+
pub fn alloc(&mut self, value: T) -> Id<T> {
95+
let raw = self.data.len();
96+
self.data.push(value);
97+
Id::from(raw as u32)
98+
}
99+
100+
pub fn alloc_iter<I: Iterator<Item = T>>(&mut self, values: I) -> Range<Id<T>> {
101+
let start = Id::from(self.data.len() as u32);
102+
values.for_each(|v| {
103+
self.alloc(v);
104+
});
105+
let end = Id::from(self.data.len() as u32);
106+
Range { start, end }
107+
}
108+
}
109+
110+
impl<T> Index<Id<T>> for Arena<T> {
111+
type Output = T;
112+
fn index(&self, id: Id<T>) -> &T {
113+
&self.data[id.raw as usize]
114+
}
115+
}
116+
117+
impl<T> Index<Range<Id<T>>> for Arena<T> {
118+
type Output = [T];
119+
fn index(&self, id: Range<Id<T>>) -> &[T] {
120+
&self.data[(id.start.raw as usize)..(id.end.raw as usize)]
121+
}
122+
}

src/internal/assignment.rs

-50
This file was deleted.

0 commit comments

Comments
 (0)