Skip to content

Commit

Permalink
release: version 0.2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
mpizenberg committed Jun 30, 2021
2 parents 0a5a94c + 57027b3 commit 717289b
Show file tree
Hide file tree
Showing 21 changed files with 1,315 additions and 778 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/cargo_publish_dry_run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Runs `cargo publish --dry-run` before another release

name: Check crate publishing works
on:
pull_request:
branches: [ release ]

env:
CARGO_TERM_COLOR: always

jobs:
cargo_publish_dry_run:
name: Publishing works
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install stable Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal

- name: Get Cargo version
id: cargo_version
run: echo "::set-output name=version::$(cargo -V | tr -d ' ')"
shell: bash

- name: Download cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}-${{ hashFiles('Cargo.toml') }}
restore-keys: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}

- name: Run `cargo publish --dry-run`
run: cargo publish --dry-run
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI
on:
pull_request:
push:
branches: [ master, dev ]
branches: [ release, dev ]
schedule: [ cron: "0 6 * * 4" ]

env:
Expand Down
43 changes: 38 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,37 @@

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

## Unreleased [(diff)][diff-unreleased]
## Unreleased [(diff)][unreleased-diff]

## [0.2.0] - 2020-11-19 - [(diff with 0.1.0)][diff-0.2.0]
## [0.2.1] - 2021-06-30 - [(diff with 0.2.0)][0.2.0-diff]

This release is focused on performance improvements and code readability, without any change to the public API.

The code tends to be simpler around tricky parts of the algorithm such as conflict resolution.
Some data structures have been rewritten (with no unsafe) to lower memory usage.
Depending on scenarios, version 0.2.1 is 3 to 8 times faster than 0.2.0.
As an example, solving all elm package versions existing went from 580ms to 175ms on my laptop.
While solving a specific subset of packages from crates.io went from 2.5s to 320ms on my laptop.

Below are listed all the important changes in the internal parts of the API.

#### Added

- New `SmallVec` data structure (with no unsafe) using fixed size arrays for up to 2 entries.
- New `SmallMap` data structure (with no unsafe) using fixed size arrays for up to 2 entries.
- New `Arena` data structure (with no unsafe) backed by a `Vec` and indexed with `Id<T>` where `T` is phantom data.

#### Changed

- Updated the `large_case` benchmark to run with both u16 and string package identifiers in registries.
- 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`.
- Save satisfier indices of each package involved in an incompatibility when looking for its satisfier. This speeds up the search for the previous satisfier.
- Early unit propagation loop restart at the first conflict found instead of continuing evaluation for the current package.
- Index incompatibilities by package in a hash map instead of using a vec.
- Keep track of already contradicted incompatibilities in a `Set` until the next backtrack to speed up unit propagation.
- 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.

## [0.2.0] - 2020-11-19 - [(diff with 0.1.0)][0.1.0-diff]

This release brings many important improvements to PubGrub.
The gist of it is:
Expand Down Expand Up @@ -78,7 +106,9 @@ The gist of it is:

#### Changed

- 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.
- 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.
- Using SPDX license identifiers instead of MPL-2.0 classic file headers.
- `State.incompatibilities` is now wrapped inside a `Rc`.
- `DecisionLevel(u32)` is used in place of `usize` for partial solution decision levels.
Expand Down Expand Up @@ -131,7 +161,10 @@ The gist of it is:
- `.gitignore` configured for a Rust project.
- `.github/workflows/` CI to automatically build, test and document on push and pull requests.

[0.2.1]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.1
[0.2.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.0
[0.1.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.1.0
[diff-unreleased]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev
[diff-0.2.0]: https://github.com/mpizenberg/elm-pointer-events/compare/v0.1.0...v0.2.0

[unreleased-diff]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev
[0.2.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.2.0...v0.2.1
[0.1.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.1.0...v0.2.0
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

[package]
name = "pubgrub"
version = "0.2.0"
authors = ["Matthieu Pizenberg <[email protected]>", "Alex Tokarev <[email protected]>", "Jacob Finkelman <[email protected]>"]
version = "0.2.1"
authors = [
"Matthieu Pizenberg <[email protected]>",
"Alex Tokarev <[email protected]>",
"Jacob Finkelman <[email protected]>",
]
edition = "2018"
description = "PubGrub version solving algorithm"
readme = "README.md"
repository = "https://github.com/mpizenberg/pubgrub-rs"
repository = "https://github.com/pubgrub-rs/pubgrub"
license = "MPL-2.0"
keywords = ["dependency", "pubgrub", "semver", "solver", "version"]
categories = ["algorithms"]
Expand All @@ -22,8 +26,8 @@ serde = { version = "1.0", features = ["derive"], optional = true }

[dev-dependencies]
proptest = "0.10.1"
ron="0.6"
varisat="0.2.2"
ron = "0.6"
varisat = "0.2.2"
criterion = "0.3"

[[bench]]
Expand Down
46 changes: 30 additions & 16 deletions benches/large_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,44 @@ use std::time::Duration;
extern crate criterion;
use self::criterion::*;

use pubgrub::package::Package;
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::NumberVersion;
use pubgrub::version::{NumberVersion, SemanticVersion, Version};
use serde::de::Deserialize;
use std::hash::Hash;

fn bench<'a, P: Package + Deserialize<'a>, V: Version + Hash + Deserialize<'a>>(
b: &mut Bencher,
case: &'a str,
) {
let dependency_provider: OfflineDependencyProvider<P, V> = ron::de::from_str(&case).unwrap();

b.iter(|| {
for p in dependency_provider.packages() {
for n in dependency_provider.versions(p).unwrap() {
let _ = resolve(&dependency_provider, p.clone(), n.clone());
}
}
});
}

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

for case in std::fs::read_dir("test-examples").unwrap() {
let case = case.unwrap().path();

group.bench_function(
format!("{}", case.file_name().unwrap().to_string_lossy()),
|b| {
let s = std::fs::read_to_string(&case).unwrap();
let dependency_provider: OfflineDependencyProvider<u16, NumberVersion> =
ron::de::from_str(&s).unwrap();

b.iter(|| {
for &n in dependency_provider.versions(&0).unwrap() {
let _ = resolve(&dependency_provider, 0, n);
}
});
},
);
let name = case.file_name().unwrap().to_string_lossy();
let data = std::fs::read_to_string(&case).unwrap();
if name.ends_with("u16_NumberVersion.ron") {
group.bench_function(name, |b| {
bench::<u16, NumberVersion>(b, &data);
});
} else if name.ends_with("str_SemanticVersion.ron") {
group.bench_function(name, |b| {
bench::<&str, SemanticVersion>(b, &data);
});
}
}

group.finish();
Expand Down
122 changes: 122 additions & 0 deletions src/internal/arena.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::{
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
ops::{Index, Range},
};

/// The index of a value allocated in an arena that holds `T`s.
///
/// The Clone, Copy and other traits are defined manually because
/// deriving them adds some additional constraints on the `T` generic type
/// that we actually don't need since it is phantom.
///
/// <https://github.com/rust-lang/rust/issues/26925>
pub struct Id<T> {
raw: u32,
_ty: PhantomData<fn() -> T>,
}

impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
*self
}
}

impl<T> Copy for Id<T> {}

impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Id<T>) -> bool {
self.raw == other.raw
}
}

impl<T> Eq for Id<T> {}

impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.raw.hash(state)
}
}

impl<T> fmt::Debug for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut type_name = std::any::type_name::<T>();
if let Some(id) = type_name.rfind(':') {
type_name = &type_name[id + 1..]
}
write!(f, "Id::<{}>({})", type_name, self.raw)
}
}

impl<T> Id<T> {
pub fn into_raw(self) -> usize {
self.raw as usize
}
fn from(n: u32) -> Self {
Self {
raw: n as u32,
_ty: PhantomData,
}
}
pub fn range_to_iter(range: Range<Self>) -> impl Iterator<Item = Self> {
let start = range.start.raw;
let end = range.end.raw;
(start..end).map(Self::from)
}
}

/// Yet another index-based arena.
///
/// An arena is a kind of simple grow-only allocator, backed by a `Vec`
/// where all items have the same lifetime, making it easier
/// to have references between those items.
/// They are all dropped at once when the arena is dropped.
#[derive(Clone, PartialEq, Eq)]
pub struct Arena<T> {
data: Vec<T>,
}

impl<T: fmt::Debug> fmt::Debug for Arena<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Arena")
.field("len", &self.data.len())
.field("data", &self.data)
.finish()
}
}

impl<T> Arena<T> {
pub fn new() -> Arena<T> {
Arena { data: Vec::new() }
}

pub fn alloc(&mut self, value: T) -> Id<T> {
let raw = self.data.len();
self.data.push(value);
Id::from(raw as u32)
}

pub fn alloc_iter<I: Iterator<Item = T>>(&mut self, values: I) -> Range<Id<T>> {
let start = Id::from(self.data.len() as u32);
values.for_each(|v| {
self.alloc(v);
});
let end = Id::from(self.data.len() as u32);
Range { start, end }
}
}

impl<T> Index<Id<T>> for Arena<T> {
type Output = T;
fn index(&self, id: Id<T>) -> &T {
&self.data[id.raw as usize]
}
}

impl<T> Index<Range<Id<T>>> for Arena<T> {
type Output = [T];
fn index(&self, id: Range<Id<T>>) -> &[T] {
&self.data[(id.start.raw as usize)..(id.end.raw as usize)]
}
}
50 changes: 0 additions & 50 deletions src/internal/assignment.rs

This file was deleted.

Loading

0 comments on commit 717289b

Please sign in to comment.