Skip to content

Commit

Permalink
Release 0.6 with newer hashbrown and no mod raw
Browse files Browse the repository at this point in the history
Fixes #21 so crate will work on Rust 1.82.

We can't upgrade to `hashbrown 0.15` since it removes the `raw` table
support. This is known upstream, and may just be something we have to
live with:

  rust-lang/hashbrown#545 (comment)

To align with `hashbrown`, and given this is a breaking change
regardless, we remove the `raw` module just like `hashbrown` has. This
should hopefully increase the chances that one day we can move to
`hashbrown`'s new lower-level interface (`HashTable`) without a breaking
change.

This release also removes the `nightly` feature because it was mostly
useless.
  • Loading branch information
jonhoo committed Oct 19, 2024
1 parent 80613a9 commit 1da2130
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 319 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,15 @@ jobs:
# intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4
# --feature-powerset runs for every combination of features
- name: cargo hack
run: cargo hack --feature-powerset --exclude-features nightly check
run: cargo hack --feature-powerset check
msrv:
# check that we can build using the minimal rust version that is specified by this crate
runs-on: ubuntu-latest
# we use a matrix here just because env can't be used in job names
# https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
strategy:
matrix:
msrv: ["1.60.0"] # 2021 edition requires 1.56
msrv: ["1.63.0"]
name: ubuntu / ${{ matrix.msrv }}
steps:
- uses: actions/checkout@v4
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ jobs:
run: cargo generate-lockfile
# https://twitter.com/jonhoo/status/1571290371124260865
- name: cargo test --locked
run: cargo test --locked --features serde,rayon,raw --all-targets
run: cargo test --locked --all-features --all-targets
# https://github.com/rust-lang/cargo/issues/6669
- name: cargo test --doc
run: cargo test --locked --features serde,rayon,raw --doc
run: cargo test --locked --all-features --doc
minimal:
# This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure
# that this crate is compatible with the minimal version that this crate and its dependencies
Expand Down Expand Up @@ -81,7 +81,7 @@ jobs:
- name: cargo update -Zminimal-versions
run: cargo +nightly update -Zminimal-versions
- name: cargo test
run: cargo test --locked --features serde,rayon,raw --all-targets
run: cargo test --locked --all-features --all-targets
os-check:
# run cargo test on mac and windows
runs-on: ${{ matrix.os }}
Expand All @@ -106,7 +106,7 @@ jobs:
if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile
- name: cargo test
run: cargo test --locked --features serde,rayon,raw --all-targets
run: cargo test --locked --all-features --all-targets
coverage:
# use llvm-cov to build and collect coverage and outputs in a format that
# is compatible with codecov.io
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
if: hashFiles('Cargo.lock') == ''
run: cargo generate-lockfile
- name: cargo llvm-cov
run: cargo llvm-cov --locked --features serde,rayon,raw --lcov --output-path lcov.info
run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info
- name: Record Rust version
run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
- name: Upload to codecov.io
Expand Down
13 changes: 5 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "griddle"
version = "0.5.2"
version = "0.6.0"
authors = ["Jon Gjengset <jon@thesquareplanet.com>"]
edition = "2018"
edition = "2021"
license = "MIT OR Apache-2.0"

readme = "README.md"
Expand All @@ -14,9 +14,9 @@ categories = ["data-structures", "no-std"]

[dependencies]
# For the default hasher
ahash_ = { version = "0.7.0", default-features = false, optional = true, package = "ahash" }
ahash_ = { version = "0.8.0", default-features = false, optional = true, package = "ahash" }

hashbrown = { version = "0.11.2", default-features = false, features = ["raw"] }
hashbrown = { version = "0.14.0", default-features = false, features = ["raw"] }

# For external trait impls
rayon_ = { version = "1.3.0", optional = true, package = "rayon" }
Expand All @@ -37,17 +37,14 @@ ahash-compile-time-rng = ["ahash_/compile-time-rng"]
ahash = [ "ahash_", "hashbrown/ahash" ]
serde = [ "serde_", "hashbrown/serde" ]
rayon = [ "rayon_", "hashbrown/rayon" ]
raw = []

# Enables usage of `#[inline]` on far more functions than by default in this
# crate. This may lead to a performance increase but often comes at a compile
# time cost.
inline-more = [ "hashbrown/inline-more" ]

nightly = []

[package.metadata.docs.rs]
features = ["rayon", "serde", "raw"]
features = ["rayon", "serde"]

[[bench]]
name = "vroom"
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,19 @@ work required to find the buckets that hold elements that must be moved.
## Implementation

Griddle uses the
[`hashbrown::raw`](https://docs.rs/hashbrown/0.8/hashbrown/raw/index.html)
[`hashbrown::raw`](https://docs.rs/hashbrown/0.14/hashbrown/raw/index.html)
API, which allows it to take advantage of all the awesome work that has
gone into making `hashbrown` as fast as it is. The key different parts
of griddle live in `src/raw/mod.rs`.
of griddle live in `src/raw/mod.rs`. The `raw` API was [removed in
`hashbrown` 0.15](https://github.com/rust-lang/hashbrown/issues/545), so
Griddle is stuck on 0.14 for now.

Griddle aims to stick as closely to `hashbrown` as it can, both in terms
of code and API. `src/map.rs` and `src/set.rs` are virtually identical
to the equivalent files in `hashbrown` (I encourage you to diff them!),
without only some (currently;
[#4](https://github.com/jonhoo/griddle/issues/4)) unsupported API
removed. Like `hashbrown`, griddle exposes a `raw` module, which lets
you build your own map implementation on top of griddle's table variant.
removed.

## Why "griddle"?

Expand Down
43 changes: 0 additions & 43 deletions src/external_trait_impls/rayon/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,55 +328,12 @@ where
#[cfg(test)]
mod test_par_map {
use alloc::vec::Vec;
use core::hash::{Hash, Hasher};
use core::sync::atomic::{AtomicUsize, Ordering};

use rayon_::prelude::*;

use crate::hash_map::HashMap;

struct Dropable<'a> {
k: usize,
counter: &'a AtomicUsize,
}

impl Dropable<'_> {
fn new(k: usize, counter: &AtomicUsize) -> Dropable<'_> {
counter.fetch_add(1, Ordering::Relaxed);

Dropable { k, counter }
}
}

impl Drop for Dropable<'_> {
fn drop(&mut self) {
self.counter.fetch_sub(1, Ordering::Relaxed);
}
}

impl Clone for Dropable<'_> {
fn clone(&self) -> Self {
Dropable::new(self.k, self.counter)
}
}

impl Hash for Dropable<'_> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.k.hash(state)
}
}

impl PartialEq for Dropable<'_> {
fn eq(&self, other: &Self) -> bool {
self.k == other.k
}
}

impl Eq for Dropable<'_> {}

#[test]
fn test_empty_iter() {
let mut m: HashMap<isize, bool> = HashMap::new();
Expand Down
4 changes: 2 additions & 2 deletions src/external_trait_impls/rayon/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rayon_::iter::{
};

/// Parallel iterator which returns a raw pointer to every full bucket in the table.
pub struct RawParIter<T> {
pub(crate) struct RawParIter<T> {
iter: hashbrown::raw::rayon::RawParIter<T>,
leftovers: Option<hashbrown::raw::rayon::RawParIter<T>>,
}
Expand Down Expand Up @@ -50,7 +50,7 @@ impl<T> ParallelIterator for RawParIter<T> {
impl<T> RawTable<T> {
/// Returns a parallel iterator over the elements in a `RawTable`.
#[cfg_attr(feature = "inline-more", inline)]
pub unsafe fn par_iter(&self) -> RawParIter<T> {
pub(crate) unsafe fn par_iter(&self) -> RawParIter<T> {
RawParIter {
iter: self.main().par_iter(),
leftovers: self.leftovers().map(|t| t.iter().into()),
Expand Down
20 changes: 0 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,6 @@ extern crate std;
#[cfg_attr(test, macro_use)]
extern crate alloc;

#[cfg(feature = "raw")]
/// Experimental and unsafe `RawTable` API. This module is only available if the
/// `raw` feature is enabled.
pub mod raw {
#[path = "mod.rs"]
mod inner;
pub use inner::*;

#[cfg(feature = "rayon")]
/// [rayon]-based parallel iterator types for raw hash tables.
/// You will rarely need to interact with it directly unless you have need
/// to name one of the iterator types.
///
/// [rayon]: https://docs.rs/rayon/1.0/rayon
pub mod rayon {
pub use crate::external_trait_impls::rayon::raw::*;
}
}
#[cfg(not(feature = "raw"))]
#[allow(dead_code)]
mod raw;

mod external_trait_impls;
Expand Down
105 changes: 4 additions & 101 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3171,7 +3171,6 @@ mod test_map {
use super::DefaultHashBuilder;
use super::Entry::{Occupied, Vacant};
use super::{HashMap, RawEntryMut};
use crate::TryReserveError::*;
use rand::{rngs::SmallRng, Rng, SeedableRng};
use std::cell::RefCell;
use std::usize;
Expand Down Expand Up @@ -4327,11 +4326,11 @@ mod test_map {
let key = "hello there";
let value = "value goes here";
assert!(a.is_empty());
a.insert(key.clone(), value.clone());
a.insert(key, value);
assert_eq!(a.len(), 1);
assert_eq!(a[key], value);

match a.entry(key.clone()) {
match a.entry(key) {
Vacant(_) => panic!(),
Occupied(e) => assert_eq!(key, *e.key()),
}
Expand All @@ -4346,11 +4345,11 @@ mod test_map {
let value = "value goes here";

assert!(a.is_empty());
match a.entry(key.clone()) {
match a.entry(key) {
Occupied(_) => panic!(),
Vacant(e) => {
assert_eq!(key, *e.key());
e.insert(value.clone());
e.insert(value);
}
}
assert_eq!(a.len(), 1);
Expand Down Expand Up @@ -4446,102 +4445,6 @@ mod test_map {
}
}

#[test]
#[cfg_attr(miri, ignore)] // FIXME: no OOM signalling (https://github.com/rust-lang/miri/issues/613)
fn test_try_reserve() {
let mut empty_bytes: HashMap<u8, u8> = HashMap::new();

const MAX_USIZE: usize = usize::MAX;

if let Err(CapacityOverflow) = empty_bytes.try_reserve(MAX_USIZE) {
} else {
panic!("usize::MAX should trigger an overflow!");
}

if let Err(AllocError { .. }) = empty_bytes.try_reserve(MAX_USIZE / 8) {
} else {
// This may succeed if there is enough free memory. Attempt to
// allocate a second hashmap to ensure the allocation will fail.
let mut empty_bytes2: HashMap<u8, u8> = HashMap::new();
if let Err(AllocError { .. }) = empty_bytes2.try_reserve(MAX_USIZE / 8) {
} else {
panic!("usize::MAX / 8 should trigger an OOM!");
}
}
}

#[test]
#[cfg(feature = "raw")]
fn test_into_iter_refresh() {
use core::hash::{BuildHasher, Hash, Hasher};

#[cfg(miri)]
const N: usize = 32;
#[cfg(not(miri))]
const N: usize = 128;

let mut rng = rand::thread_rng();
for n in 0..N {
let mut m = HashMap::new();
for i in 0..n {
assert!(m.insert(i, 2 * i).is_none());
}
let hasher = m.hasher().clone();

let mut it = unsafe { m.table.iter() };
assert_eq!(it.len(), n);

let mut i = 0;
let mut left = n;
let mut removed = Vec::new();
loop {
// occasionally remove some elements
if i < n && rng.gen_bool(0.1) {
let mut hsh = hasher.build_hasher();
i.hash(&mut hsh);
let hash = hsh.finish();

unsafe {
let e = m.table.find(hash, |q| q.0.eq(&i));
if let Some(e) = e {
it.reflect_remove(&e);
let t = m.table.remove(e);
removed.push(t);
left -= 1;
} else {
assert!(removed.contains(&(i, 2 * i)), "{} not in {:?}", i, removed);
let e = m.table.insert(
hash,
(i, 2 * i),
super::make_hasher::<usize, _, _, _>(&hasher),
);
it.reflect_insert(&e);
if let Some(p) = removed.iter().position(|e| e == &(i, 2 * i)) {
removed.swap_remove(p);
}
left += 1;
}
}
}

let e = it.next();
if e.is_none() {
break;
}
assert!(i < n);
let t = unsafe { e.unwrap().as_ref() };
assert!(!removed.contains(t));
let (k, v) = t;
assert_eq!(*v, 2 * k);
i += 1;
}
assert!(i <= n);

// just for safety:
assert_eq!(m.table.len(), left);
}
}

#[test]
fn test_raw_entry() {
use super::RawEntryMut::{Occupied, Vacant};
Expand Down
Loading

0 comments on commit 1da2130

Please sign in to comment.