forked from openkylin/rust-hashbrown
Import Upstream version 0.12.3
This commit is contained in:
commit
6323e923d8
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"git": {
|
||||
"sha1": "1d2c1a81d1b53285decbd64410a21a90112613d7"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
|
@ -0,0 +1 @@
|
|||
debian/patches
|
|
@ -0,0 +1 @@
|
|||
series
|
|
@ -0,0 +1 @@
|
|||
2
|
|
@ -0,0 +1 @@
|
|||
disable-alloc.diff
|
|
@ -0,0 +1,113 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.56.0"
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
authors = ["Amanieu d'Antras <amanieu@gmail.com>"]
|
||||
exclude = [
|
||||
".github",
|
||||
"/ci/*",
|
||||
]
|
||||
description = "A Rust port of Google's SwissTable hash map"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"hash",
|
||||
"no_std",
|
||||
"hashmap",
|
||||
"swisstable",
|
||||
]
|
||||
categories = [
|
||||
"data-structures",
|
||||
"no-std",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/hashbrown"
|
||||
resolver = "2"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"nightly",
|
||||
"rayon",
|
||||
"serde",
|
||||
"raw",
|
||||
]
|
||||
|
||||
[dependencies.ahash]
|
||||
version = "0.7.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.alloc]
|
||||
version = "1.0.0"
|
||||
optional = true
|
||||
package = "rustc-std-workspace-alloc"
|
||||
|
||||
[dependencies.bumpalo]
|
||||
version = "3.5.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.compiler_builtins]
|
||||
version = "0.1.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.core]
|
||||
version = "1.0.0"
|
||||
optional = true
|
||||
package = "rustc-std-workspace-core"
|
||||
|
||||
[dependencies.rayon]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.25"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.doc-comment]
|
||||
version = "0.3.1"
|
||||
|
||||
[dev-dependencies.fnv]
|
||||
version = "1.0.7"
|
||||
|
||||
[dev-dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.3"
|
||||
features = ["small_rng"]
|
||||
|
||||
[dev-dependencies.rayon]
|
||||
version = "1.0"
|
||||
|
||||
[dev-dependencies.serde_test]
|
||||
version = "1.0"
|
||||
|
||||
[features]
|
||||
ahash-compile-time-rng = ["ahash/compile-time-rng"]
|
||||
default = [
|
||||
"ahash",
|
||||
"inline-more",
|
||||
]
|
||||
inline-more = []
|
||||
nightly = []
|
||||
raw = []
|
||||
rustc-dep-of-std = [
|
||||
"nightly",
|
||||
"core",
|
||||
"compiler_builtins",
|
||||
"alloc",
|
||||
"rustc-internal-api",
|
||||
]
|
||||
rustc-internal-api = []
|
|
@ -0,0 +1,402 @@
|
|||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.12.3] - 2022-07-17
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed double-drop in `RawTable::clone_from`. (#348)
|
||||
|
||||
## [v0.12.2] - 2022-07-09
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Entry` API for `HashSet`. (#342)
|
||||
- Added `Extend<&'a (K, V)> for HashMap<K, V, S, A>`. (#340)
|
||||
- Added length-based short-circuiting for hash table iteration. (#338)
|
||||
- Added a function to access the `RawTable` of a `HashMap`. (#335)
|
||||
|
||||
## Changed
|
||||
|
||||
- Edited `do_alloc` to reduce LLVM IR generated. (#341)
|
||||
|
||||
## [v0.12.1] - 2022-05-02
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed underflow in `RawIterRange::size_hint`. (#325)
|
||||
- Fixed the implementation of `Debug` for `ValuesMut` and `IntoValues`. (#325)
|
||||
|
||||
## [v0.12.0] - 2022-01-17
|
||||
|
||||
## Added
|
||||
|
||||
- Added `From<[T; N]>` and `From<[(K, V); N]>` for `HashSet` and `HashMap` respectively. (#297)
|
||||
- Added an `allocator()` getter to HashMap and HashSet. (#257)
|
||||
- Added `insert_unique_unchecked` to `HashMap` and `HashSet`. (#293)
|
||||
- Added `into_keys` and `into_values` to HashMap. (#295)
|
||||
- Implement `From<array>` on `HashSet` and `HashMap`. (#298)
|
||||
- Added `entry_ref` API to `HashMap`. (#201)
|
||||
|
||||
## Changed
|
||||
|
||||
- Bumped minimum Rust version to 1.56.1 and edition to 2021.
|
||||
- Use u64 for the GroupWord on WebAssembly. (#271)
|
||||
- Optimized `find`. (#279)
|
||||
- Made rehashing and resizing less generic to reduce compilation time. (#282)
|
||||
- Inlined small functions. (#283)
|
||||
- Use `BuildHasher::hash_one` when `feature = "nightly"` is enabled. (#292)
|
||||
- Relaxed the bounds on `Debug` for `HashSet`. (#296)
|
||||
- Rename `get_each_mut` to `get_many_mut` and align API with the stdlib. (#291)
|
||||
- Don't hash the key when searching in an empty table. (#305)
|
||||
|
||||
## Fixed
|
||||
|
||||
- Guard against allocations exceeding isize::MAX. (#268)
|
||||
- Made `RawTable::insert_no_grow` unsafe. (#254)
|
||||
- Inline `static_empty`. (#280)
|
||||
- Fixed trait bounds on Send/Sync impls. (#303)
|
||||
|
||||
## [v0.11.2] - 2021-03-25
|
||||
|
||||
## Fixed
|
||||
|
||||
- Added missing allocator type parameter to `HashMap`'s and `HashSet`'s `Clone` impls. (#252)
|
||||
|
||||
## [v0.11.1] - 2021-03-20
|
||||
|
||||
## Fixed
|
||||
|
||||
- Added missing `pub` modifier to `BumpWrapper`. (#251)
|
||||
|
||||
## [v0.11.0] - 2021-03-14
|
||||
|
||||
## Added
|
||||
- Added safe `try_insert_no_grow` method to `RawTable`. (#229)
|
||||
- Added support for `bumpalo` as an allocator without the `nightly` feature. (#231)
|
||||
- Implemented `Default` for `RawTable`. (#237)
|
||||
- Added new safe methods `RawTable::get_each_mut`, `HashMap::get_each_mut`, and
|
||||
`HashMap::get_each_key_value_mut`. (#239)
|
||||
- Added `From<HashMap<T, ()>>` for `HashSet<T>`. (#235)
|
||||
- Added `try_insert` method to `HashMap`. (#247)
|
||||
|
||||
## Changed
|
||||
- The minimum Rust version has been bumped to 1.49.0. (#230)
|
||||
- Significantly improved compilation times by reducing the amount of generated IR. (#205)
|
||||
|
||||
## Removed
|
||||
- We no longer re-export the unstable allocator items from the standard library, nor the stable shims approximating the same. (#227)
|
||||
- Removed hasher specialization support from `aHash`, which was resulting in inconsistent hashes being generated for a key. (#248)
|
||||
|
||||
## Fixed
|
||||
- Fixed union length comparison. (#228)
|
||||
|
||||
## ~~[v0.10.0] - 2021-01-16~~
|
||||
|
||||
This release was _yanked_ due to inconsistent hashes being generated with the `nightly` feature. (#248)
|
||||
|
||||
## Changed
|
||||
- Parametrized `RawTable`, `HashSet` and `HashMap` over an allocator. (#133)
|
||||
- Improved branch prediction hints on stable. (#209)
|
||||
- Optimized hashing of primitive types with AHash using specialization. (#207)
|
||||
- Only instantiate `RawTable`'s reserve functions once per key-value. (#204)
|
||||
|
||||
## [v0.9.1] - 2020-09-28
|
||||
|
||||
## Added
|
||||
- Added safe methods to `RawTable` (#202):
|
||||
- `get`: `find` and `as_ref`
|
||||
- `get_mut`: `find` and `as_mut`
|
||||
- `insert_entry`: `insert` and `as_mut`
|
||||
- `remove_entry`: `find` and `remove`
|
||||
- `erase_entry`: `find` and `erase`
|
||||
|
||||
## Changed
|
||||
- Removed `from_key_hashed_nocheck`'s `Q: Hash`. (#200)
|
||||
- Made `RawTable::drain` safe. (#201)
|
||||
|
||||
## [v0.9.0] - 2020-09-03
|
||||
|
||||
### Fixed
|
||||
- `drain_filter` now removes and yields items that do match the predicate,
|
||||
rather than items that don't. This is a **breaking change** to match the
|
||||
behavior of the `drain_filter` methods in `std`. (#187)
|
||||
|
||||
### Added
|
||||
- Added `replace_entry_with` to `OccupiedEntry`, and `and_replace_entry_with` to `Entry`. (#190)
|
||||
- Implemented `FusedIterator` and `size_hint` for `DrainFilter`. (#188)
|
||||
|
||||
### Changed
|
||||
- The minimum Rust version has been bumped to 1.36 (due to `crossbeam` dependency). (#193)
|
||||
- Updated `ahash` dependency to 0.4. (#198)
|
||||
- `HashMap::with_hasher` and `HashSet::with_hasher` are now `const fn`. (#195)
|
||||
- Removed `T: Hash + Eq` and `S: BuildHasher` bounds on `HashSet::new`,
|
||||
`with_capacity`, `with_hasher`, and `with_capacity_and_hasher`. (#185)
|
||||
|
||||
## [v0.8.2] - 2020-08-08
|
||||
|
||||
### Changed
|
||||
- Avoid closures to improve compile times. (#183)
|
||||
- Do not iterate to drop if empty. (#182)
|
||||
|
||||
## [v0.8.1] - 2020-07-16
|
||||
|
||||
### Added
|
||||
- Added `erase` and `remove` to `RawTable`. (#171)
|
||||
- Added `try_with_capacity` to `RawTable`. (#174)
|
||||
- Added methods that allow re-using a `RawIter` for `RawDrain`,
|
||||
`RawIntoIter`, and `RawParIter`. (#175)
|
||||
- Added `reflect_remove` and `reflect_insert` to `RawIter`. (#175)
|
||||
- Added a `drain_filter` function to `HashSet`. (#179)
|
||||
|
||||
### Changed
|
||||
- Deprecated `RawTable::erase_no_drop` in favor of `erase` and `remove`. (#176)
|
||||
- `insert_no_grow` is now exposed under the `"raw"` feature. (#180)
|
||||
|
||||
## [v0.8.0] - 2020-06-18
|
||||
|
||||
### Fixed
|
||||
- Marked `RawTable::par_iter` as `unsafe`. (#157)
|
||||
|
||||
### Changed
|
||||
- Reduced the size of `HashMap`. (#159)
|
||||
- No longer create tables with a capacity of 1 element. (#162)
|
||||
- Removed `K: Eq + Hash` bounds on `retain`. (#163)
|
||||
- Pulled in `HashMap` changes from rust-lang/rust (#164):
|
||||
- `extend_one` support on nightly.
|
||||
- `CollectionAllocErr` renamed to `TryReserveError`.
|
||||
- Added `HashSet::get_or_insert_owned`.
|
||||
- `Default` for `HashSet` no longer requires `T: Eq + Hash` and `S: BuildHasher`.
|
||||
|
||||
## [v0.7.2] - 2020-04-27
|
||||
|
||||
### Added
|
||||
- Added `or_insert_with_key` to `Entry`. (#152)
|
||||
|
||||
### Fixed
|
||||
- Partially reverted `Clone` optimization which was unsound. (#154)
|
||||
|
||||
### Changed
|
||||
- Disabled use of `const-random` by default, which prevented reproducible builds. (#155)
|
||||
- Optimized `repeat` function. (#150)
|
||||
- Use `NonNull` for buckets, which improves codegen for iterators. (#148)
|
||||
|
||||
## [v0.7.1] - 2020-03-16
|
||||
|
||||
### Added
|
||||
- Added `HashMap::get_key_value_mut`. (#145)
|
||||
|
||||
### Changed
|
||||
- Optimized `Clone` implementation. (#146)
|
||||
|
||||
## [v0.7.0] - 2020-01-31
|
||||
|
||||
### Added
|
||||
- Added a `drain_filter` function to `HashMap`. (#135)
|
||||
|
||||
### Changed
|
||||
- Updated `ahash` dependency to 0.3. (#141)
|
||||
- Optimized set union and intersection. (#130)
|
||||
- `raw_entry` can now be used without requiring `S: BuildHasher`. (#123)
|
||||
- `RawTable::bucket_index` can now be used under the `raw` feature. (#128)
|
||||
|
||||
## [v0.6.3] - 2019-10-31
|
||||
|
||||
### Added
|
||||
- Added an `ahash-compile-time-rng` feature (enabled by default) which allows disabling the
|
||||
`compile-time-rng` feature in `ahash` to work around a Cargo bug. (#125)
|
||||
|
||||
## [v0.6.2] - 2019-10-23
|
||||
|
||||
### Added
|
||||
- Added an `inline-more` feature (enabled by default) which allows choosing a tradeoff between
|
||||
runtime performance and compilation time. (#119)
|
||||
|
||||
## [v0.6.1] - 2019-10-04
|
||||
|
||||
### Added
|
||||
- Added `Entry::insert` and `RawEntryMut::insert`. (#118)
|
||||
|
||||
### Changed
|
||||
- `Group::static_empty` was changed from a `const` to a `static` (#116).
|
||||
|
||||
## [v0.6.0] - 2019-08-13
|
||||
|
||||
### Fixed
|
||||
- Fixed AHash accidentally depending on `std`. (#110)
|
||||
|
||||
### Changed
|
||||
- The minimum Rust version has been bumped to 1.32 (due to `rand` dependency).
|
||||
|
||||
## ~~[v0.5.1] - 2019-08-04~~
|
||||
|
||||
This release was _yanked_ due to a breaking change for users of `no-default-features`.
|
||||
|
||||
### Added
|
||||
- The experimental and unsafe `RawTable` API is available under the "raw" feature. (#108)
|
||||
- Added entry-like methods for `HashSet`. (#98)
|
||||
|
||||
### Changed
|
||||
- Changed the default hasher from FxHash to AHash. (#97)
|
||||
- `hashbrown` is now fully `no_std` on recent Rust versions (1.36+). (#96)
|
||||
|
||||
### Fixed
|
||||
- We now avoid growing the table during insertions when it wasn't necessary. (#106)
|
||||
- `RawOccupiedEntryMut` now properly implements `Send` and `Sync`. (#100)
|
||||
- Relaxed `lazy_static` version. (#92)
|
||||
|
||||
## [v0.5.0] - 2019-06-12
|
||||
|
||||
### Fixed
|
||||
- Resize with a more conservative amount of space after deletions. (#86)
|
||||
|
||||
### Changed
|
||||
- Exposed the Layout of the failed allocation in CollectionAllocErr::AllocErr. (#89)
|
||||
|
||||
## [v0.4.0] - 2019-05-30
|
||||
|
||||
### Fixed
|
||||
- Fixed `Send` trait bounds on `IterMut` not matching the libstd one. (#82)
|
||||
|
||||
## [v0.3.1] - 2019-05-30
|
||||
|
||||
### Fixed
|
||||
- Fixed incorrect use of slice in unsafe code. (#80)
|
||||
|
||||
## [v0.3.0] - 2019-04-23
|
||||
|
||||
### Changed
|
||||
- Changed shrink_to to not panic if min_capacity < capacity. (#67)
|
||||
|
||||
### Fixed
|
||||
- Worked around emscripten bug emscripten-core/emscripten-fastcomp#258. (#66)
|
||||
|
||||
## [v0.2.2] - 2019-04-16
|
||||
|
||||
### Fixed
|
||||
- Inlined non-nightly lowest_set_bit_nonzero. (#64)
|
||||
- Fixed build on latest nightly. (#65)
|
||||
|
||||
## [v0.2.1] - 2019-04-14
|
||||
|
||||
### Changed
|
||||
- Use for_each in map Extend and FromIterator. (#58)
|
||||
- Improved worst-case performance of HashSet.is_subset. (#61)
|
||||
|
||||
### Fixed
|
||||
- Removed incorrect debug_assert. (#60)
|
||||
|
||||
## [v0.2.0] - 2019-03-31
|
||||
|
||||
### Changed
|
||||
- The code has been updated to Rust 2018 edition. This means that the minimum
|
||||
Rust version has been bumped to 1.31 (2018 edition).
|
||||
|
||||
### Added
|
||||
- Added `insert_with_hasher` to the raw_entry API to allow `K: !(Hash + Eq)`. (#54)
|
||||
- Added support for using hashbrown as the hash table implementation in libstd. (#46)
|
||||
|
||||
### Fixed
|
||||
- Fixed cargo build with minimal-versions. (#45)
|
||||
- Fixed `#[may_dangle]` attributes to match the libstd `HashMap`. (#46)
|
||||
- ZST keys and values are now handled properly. (#46)
|
||||
|
||||
## [v0.1.8] - 2019-01-14
|
||||
|
||||
### Added
|
||||
- Rayon parallel iterator support (#37)
|
||||
- `raw_entry` support (#31)
|
||||
- `#[may_dangle]` on nightly (#31)
|
||||
- `try_reserve` support (#31)
|
||||
|
||||
### Fixed
|
||||
- Fixed variance on `IterMut`. (#31)
|
||||
|
||||
## [v0.1.7] - 2018-12-05
|
||||
|
||||
### Fixed
|
||||
- Fixed non-SSE version of convert_special_to_empty_and_full_to_deleted. (#32)
|
||||
- Fixed overflow in rehash_in_place. (#33)
|
||||
|
||||
## [v0.1.6] - 2018-11-17
|
||||
|
||||
### Fixed
|
||||
- Fixed compile error on nightly. (#29)
|
||||
|
||||
## [v0.1.5] - 2018-11-08
|
||||
|
||||
### Fixed
|
||||
- Fixed subtraction overflow in generic::Group::match_byte. (#28)
|
||||
|
||||
## [v0.1.4] - 2018-11-04
|
||||
|
||||
### Fixed
|
||||
- Fixed a bug in the `erase_no_drop` implementation. (#26)
|
||||
|
||||
## [v0.1.3] - 2018-11-01
|
||||
|
||||
### Added
|
||||
- Serde support. (#14)
|
||||
|
||||
### Fixed
|
||||
- Make the compiler inline functions more aggressively. (#20)
|
||||
|
||||
## [v0.1.2] - 2018-10-31
|
||||
|
||||
### Fixed
|
||||
- `clear` segfaults when called on an empty table. (#13)
|
||||
|
||||
## [v0.1.1] - 2018-10-30
|
||||
|
||||
### Fixed
|
||||
- `erase_no_drop` optimization not triggering in the SSE2 implementation. (#3)
|
||||
- Missing `Send` and `Sync` for hash map and iterator types. (#7)
|
||||
- Bug when inserting into a table smaller than the group width. (#5)
|
||||
|
||||
## v0.1.0 - 2018-10-29
|
||||
|
||||
- Initial release
|
||||
|
||||
[Unreleased]: https://github.com/rust-lang/hashbrown/compare/v0.12.3...HEAD
|
||||
[v0.12.3]: https://github.com/rust-lang/hashbrown/compare/v0.12.2...v0.12.3
|
||||
[v0.12.2]: https://github.com/rust-lang/hashbrown/compare/v0.12.1...v0.12.2
|
||||
[v0.12.1]: https://github.com/rust-lang/hashbrown/compare/v0.12.0...v0.12.1
|
||||
[v0.12.0]: https://github.com/rust-lang/hashbrown/compare/v0.11.2...v0.12.0
|
||||
[v0.11.2]: https://github.com/rust-lang/hashbrown/compare/v0.11.1...v0.11.2
|
||||
[v0.11.1]: https://github.com/rust-lang/hashbrown/compare/v0.11.0...v0.11.1
|
||||
[v0.11.0]: https://github.com/rust-lang/hashbrown/compare/v0.10.0...v0.11.0
|
||||
[v0.10.0]: https://github.com/rust-lang/hashbrown/compare/v0.9.1...v0.10.0
|
||||
[v0.9.1]: https://github.com/rust-lang/hashbrown/compare/v0.9.0...v0.9.1
|
||||
[v0.9.0]: https://github.com/rust-lang/hashbrown/compare/v0.8.2...v0.9.0
|
||||
[v0.8.2]: https://github.com/rust-lang/hashbrown/compare/v0.8.1...v0.8.2
|
||||
[v0.8.1]: https://github.com/rust-lang/hashbrown/compare/v0.8.0...v0.8.1
|
||||
[v0.8.0]: https://github.com/rust-lang/hashbrown/compare/v0.7.2...v0.8.0
|
||||
[v0.7.2]: https://github.com/rust-lang/hashbrown/compare/v0.7.1...v0.7.2
|
||||
[v0.7.1]: https://github.com/rust-lang/hashbrown/compare/v0.7.0...v0.7.1
|
||||
[v0.7.0]: https://github.com/rust-lang/hashbrown/compare/v0.6.3...v0.7.0
|
||||
[v0.6.3]: https://github.com/rust-lang/hashbrown/compare/v0.6.2...v0.6.3
|
||||
[v0.6.2]: https://github.com/rust-lang/hashbrown/compare/v0.6.1...v0.6.2
|
||||
[v0.6.1]: https://github.com/rust-lang/hashbrown/compare/v0.6.0...v0.6.1
|
||||
[v0.6.0]: https://github.com/rust-lang/hashbrown/compare/v0.5.1...v0.6.0
|
||||
[v0.5.1]: https://github.com/rust-lang/hashbrown/compare/v0.5.0...v0.5.1
|
||||
[v0.5.0]: https://github.com/rust-lang/hashbrown/compare/v0.4.0...v0.5.0
|
||||
[v0.4.0]: https://github.com/rust-lang/hashbrown/compare/v0.3.1...v0.4.0
|
||||
[v0.3.1]: https://github.com/rust-lang/hashbrown/compare/v0.3.0...v0.3.1
|
||||
[v0.3.0]: https://github.com/rust-lang/hashbrown/compare/v0.2.2...v0.3.0
|
||||
[v0.2.2]: https://github.com/rust-lang/hashbrown/compare/v0.2.1...v0.2.2
|
||||
[v0.2.1]: https://github.com/rust-lang/hashbrown/compare/v0.2.0...v0.2.1
|
||||
[v0.2.0]: https://github.com/rust-lang/hashbrown/compare/v0.1.8...v0.2.0
|
||||
[v0.1.8]: https://github.com/rust-lang/hashbrown/compare/v0.1.7...v0.1.8
|
||||
[v0.1.7]: https://github.com/rust-lang/hashbrown/compare/v0.1.6...v0.1.7
|
||||
[v0.1.6]: https://github.com/rust-lang/hashbrown/compare/v0.1.5...v0.1.6
|
||||
[v0.1.5]: https://github.com/rust-lang/hashbrown/compare/v0.1.4...v0.1.5
|
||||
[v0.1.4]: https://github.com/rust-lang/hashbrown/compare/v0.1.3...v0.1.4
|
||||
[v0.1.3]: https://github.com/rust-lang/hashbrown/compare/v0.1.2...v0.1.3
|
||||
[v0.1.2]: https://github.com/rust-lang/hashbrown/compare/v0.1.1...v0.1.2
|
||||
[v0.1.1]: https://github.com/rust-lang/hashbrown/compare/v0.1.0...v0.1.1
|
|
@ -0,0 +1,107 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.56.0"
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
authors = ["Amanieu d'Antras <amanieu@gmail.com>"]
|
||||
exclude = [
|
||||
".github",
|
||||
"/ci/*",
|
||||
]
|
||||
description = "A Rust port of Google's SwissTable hash map"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"hash",
|
||||
"no_std",
|
||||
"hashmap",
|
||||
"swisstable",
|
||||
]
|
||||
categories = [
|
||||
"data-structures",
|
||||
"no-std",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/hashbrown"
|
||||
resolver = "2"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"nightly",
|
||||
"rayon",
|
||||
"serde",
|
||||
"raw",
|
||||
]
|
||||
|
||||
[dependencies.ahash]
|
||||
version = "0.7.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.bumpalo]
|
||||
version = "3.5.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.compiler_builtins]
|
||||
version = "0.1.2"
|
||||
optional = true
|
||||
|
||||
[dependencies.core]
|
||||
version = "1.0.0"
|
||||
optional = true
|
||||
package = "rustc-std-workspace-core"
|
||||
|
||||
[dependencies.rayon]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.25"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.doc-comment]
|
||||
version = "0.3.1"
|
||||
|
||||
[dev-dependencies.fnv]
|
||||
version = "1.0.7"
|
||||
|
||||
[dev-dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.3"
|
||||
features = ["small_rng"]
|
||||
|
||||
[dev-dependencies.rayon]
|
||||
version = "1.0"
|
||||
|
||||
[dev-dependencies.serde_test]
|
||||
version = "1.0"
|
||||
|
||||
[features]
|
||||
ahash-compile-time-rng = ["ahash/compile-time-rng"]
|
||||
default = [
|
||||
"ahash",
|
||||
"inline-more",
|
||||
]
|
||||
inline-more = []
|
||||
nightly = []
|
||||
raw = []
|
||||
rustc-dep-of-std = [
|
||||
"nightly",
|
||||
"core",
|
||||
"compiler_builtins",
|
||||
"rustc-internal-api",
|
||||
]
|
||||
rustc-internal-api = []
|
|
@ -0,0 +1,60 @@
|
|||
[package]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
authors = ["Amanieu d'Antras <amanieu@gmail.com>"]
|
||||
description = "A Rust port of Google's SwissTable hash map"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-lang/hashbrown"
|
||||
readme = "README.md"
|
||||
keywords = ["hash", "no_std", "hashmap", "swisstable"]
|
||||
categories = ["data-structures", "no-std"]
|
||||
exclude = [".github", "/ci/*"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56.0"
|
||||
|
||||
[dependencies]
|
||||
# For the default hasher
|
||||
ahash = { version = "0.7.0", default-features = false, optional = true }
|
||||
|
||||
# For external trait impls
|
||||
rayon = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0.25", default-features = false, optional = true }
|
||||
|
||||
# When built as part of libstd
|
||||
core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
|
||||
compiler_builtins = { version = "0.1.2", optional = true }
|
||||
alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-alloc" }
|
||||
|
||||
# Optional support for bumpalo
|
||||
bumpalo = { version = "3.5.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1.4"
|
||||
rand = { version = "0.8.3", features = ["small_rng"] }
|
||||
rayon = "1.0"
|
||||
fnv = "1.0.7"
|
||||
serde_test = "1.0"
|
||||
doc-comment = "0.3.1"
|
||||
|
||||
[features]
|
||||
default = ["ahash", "inline-more"]
|
||||
|
||||
ahash-compile-time-rng = ["ahash/compile-time-rng"]
|
||||
nightly = []
|
||||
rustc-internal-api = []
|
||||
rustc-dep-of-std = [
|
||||
"nightly",
|
||||
"core",
|
||||
"compiler_builtins",
|
||||
"alloc",
|
||||
"rustc-internal-api",
|
||||
]
|
||||
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 = []
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["nightly", "rayon", "serde", "raw"]
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2016 Amanieu d'Antras
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,126 @@
|
|||
hashbrown
|
||||
=========
|
||||
|
||||
[![Build Status](https://github.com/rust-lang/hashbrown/actions/workflows/rust.yml/badge.svg)](https://github.com/rust-lang/hashbrown/actions)
|
||||
[![Crates.io](https://img.shields.io/crates/v/hashbrown.svg)](https://crates.io/crates/hashbrown)
|
||||
[![Documentation](https://docs.rs/hashbrown/badge.svg)](https://docs.rs/hashbrown)
|
||||
[![Rust](https://img.shields.io/badge/rust-1.56.1%2B-blue.svg?maxAge=3600)](https://github.com/rust-lang/hashbrown)
|
||||
|
||||
This crate is a Rust port of Google's high-performance [SwissTable] hash
|
||||
map, adapted to make it a drop-in replacement for Rust's standard `HashMap`
|
||||
and `HashSet` types.
|
||||
|
||||
The original C++ version of SwissTable can be found [here], and this
|
||||
[CppCon talk] gives an overview of how the algorithm works.
|
||||
|
||||
Since Rust 1.36, this is now the `HashMap` implementation for the Rust standard
|
||||
library. However you may still want to use this crate instead since it works
|
||||
in environments without `std`, such as embedded systems and kernels.
|
||||
|
||||
[SwissTable]: https://abseil.io/blog/20180927-swisstables
|
||||
[here]: https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h
|
||||
[CppCon talk]: https://www.youtube.com/watch?v=ncHmEUmJZf4
|
||||
|
||||
## [Change log](CHANGELOG.md)
|
||||
|
||||
## Features
|
||||
|
||||
- Drop-in replacement for the standard library `HashMap` and `HashSet` types.
|
||||
- Uses [AHash](https://github.com/tkaitchuck/aHash) as the default hasher, which is much faster than SipHash.
|
||||
However, AHash does *not provide the same level of HashDoS resistance* as SipHash, so if that is important to you, you might want to consider using a different hasher.
|
||||
- Around 2x faster than the previous standard library `HashMap`.
|
||||
- Lower memory usage: only 1 byte of overhead per entry instead of 8.
|
||||
- Compatible with `#[no_std]` (but requires a global allocator with the `alloc` crate).
|
||||
- Empty hash maps do not allocate any memory.
|
||||
- SIMD lookups to scan multiple hash entries in parallel.
|
||||
|
||||
## Performance
|
||||
|
||||
Compared to the previous implementation of `std::collections::HashMap` (Rust 1.35).
|
||||
|
||||
With the hashbrown default AHash hasher:
|
||||
|
||||
| name | oldstdhash ns/iter | hashbrown ns/iter | diff ns/iter | diff % | speedup |
|
||||
|:------------------------|:-------------------:|------------------:|:------------:|---------:|---------|
|
||||
| insert_ahash_highbits | 18,865 | 8,020 | -10,845 | -57.49% | x 2.35 |
|
||||
| insert_ahash_random | 19,711 | 8,019 | -11,692 | -59.32% | x 2.46 |
|
||||
| insert_ahash_serial | 19,365 | 6,463 | -12,902 | -66.63% | x 3.00 |
|
||||
| insert_erase_ahash_highbits | 51,136 | 17,916 | -33,220 | -64.96% | x 2.85 |
|
||||
| insert_erase_ahash_random | 51,157 | 17,688 | -33,469 | -65.42% | x 2.89 |
|
||||
| insert_erase_ahash_serial | 45,479 | 14,895 | -30,584 | -67.25% | x 3.05 |
|
||||
| iter_ahash_highbits | 1,399 | 1,092 | -307 | -21.94% | x 1.28 |
|
||||
| iter_ahash_random | 1,586 | 1,059 | -527 | -33.23% | x 1.50 |
|
||||
| iter_ahash_serial | 3,168 | 1,079 | -2,089 | -65.94% | x 2.94 |
|
||||
| lookup_ahash_highbits | 32,351 | 4,792 | -27,559 | -85.19% | x 6.75 |
|
||||
| lookup_ahash_random | 17,419 | 4,817 | -12,602 | -72.35% | x 3.62 |
|
||||
| lookup_ahash_serial | 15,254 | 3,606 | -11,648 | -76.36% | x 4.23 |
|
||||
| lookup_fail_ahash_highbits | 21,187 | 4,369 | -16,818 | -79.38% | x 4.85 |
|
||||
| lookup_fail_ahash_random | 21,550 | 4,395 | -17,155 | -79.61% | x 4.90 |
|
||||
| lookup_fail_ahash_serial | 19,450 | 3,176 | -16,274 | -83.67% | x 6.12 |
|
||||
|
||||
|
||||
With the libstd default SipHash hasher:
|
||||
|
||||
|name | oldstdhash ns/iter | hashbrown ns/iter | diff ns/iter | diff % | speedup |
|
||||
|:------------------------|:-------------------:|------------------:|:------------:|---------:|---------|
|
||||
|insert_std_highbits |19,216 |16,885 | -2,331 | -12.13% | x 1.14 |
|
||||
|insert_std_random |19,179 |17,034 | -2,145 | -11.18% | x 1.13 |
|
||||
|insert_std_serial |19,462 |17,493 | -1,969 | -10.12% | x 1.11 |
|
||||
|insert_erase_std_highbits |50,825 |35,847 | -14,978 | -29.47% | x 1.42 |
|
||||
|insert_erase_std_random |51,448 |35,392 | -16,056 | -31.21% | x 1.45 |
|
||||
|insert_erase_std_serial |87,711 |38,091 | -49,620 | -56.57% | x 2.30 |
|
||||
|iter_std_highbits |1,378 |1,159 | -219 | -15.89% | x 1.19 |
|
||||
|iter_std_random |1,395 |1,132 | -263 | -18.85% | x 1.23 |
|
||||
|iter_std_serial |1,704 |1,105 | -599 | -35.15% | x 1.54 |
|
||||
|lookup_std_highbits |17,195 |13,642 | -3,553 | -20.66% | x 1.26 |
|
||||
|lookup_std_random |17,181 |13,773 | -3,408 | -19.84% | x 1.25 |
|
||||
|lookup_std_serial |15,483 |13,651 | -1,832 | -11.83% | x 1.13 |
|
||||
|lookup_fail_std_highbits |20,926 |13,474 | -7,452 | -35.61% | x 1.55 |
|
||||
|lookup_fail_std_random |21,766 |13,505 | -8,261 | -37.95% | x 1.61 |
|
||||
|lookup_fail_std_serial |19,336 |13,519 | -5,817 | -30.08% | x 1.43 |
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
hashbrown = "0.12"
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```rust
|
||||
use hashbrown::HashMap;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(1, "one");
|
||||
```
|
||||
## Flags
|
||||
This crate has the following Cargo features:
|
||||
|
||||
- `nightly`: Enables nightly-only features including: `#[may_dangle]`.
|
||||
- `serde`: Enables serde serialization support.
|
||||
- `rayon`: Enables rayon parallel iterator support.
|
||||
- `raw`: Enables access to the experimental and unsafe `RawTable` API.
|
||||
- `inline-more`: Adds inline hints to most functions, improving run-time performance at the cost
|
||||
of compilation time. (enabled by default)
|
||||
- `bumpalo`: Provides a `BumpWrapper` type which allows `bumpalo` to be used for memory allocation.
|
||||
- `ahash`: Compiles with ahash as default hasher. (enabled by default)
|
||||
- `ahash-compile-time-rng`: Activates the `compile-time-rng` feature of ahash. For targets with no random number generator
|
||||
this pre-generates seeds at compile time and embeds them as constants. See [aHash's documentation](https://github.com/tkaitchuck/aHash#flags) (disabled by default)
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of:
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
|
||||
additional terms or conditions.
|
|
@ -0,0 +1,331 @@
|
|||
// This benchmark suite contains some benchmarks along a set of dimensions:
|
||||
// Hasher: std default (SipHash) and crate default (AHash).
|
||||
// Int key distribution: low bit heavy, top bit heavy, and random.
|
||||
// Task: basic functionality: insert, insert_erase, lookup, lookup_fail, iter
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
use hashbrown::hash_map::DefaultHashBuilder;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use std::{
|
||||
collections::hash_map::RandomState,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
|
||||
const SIZE: usize = 1000;
|
||||
|
||||
// The default hashmap when using this crate directly.
|
||||
type AHashMap<K, V> = HashMap<K, V, DefaultHashBuilder>;
|
||||
// This uses the hashmap from this crate with the default hasher of the stdlib.
|
||||
type StdHashMap<K, V> = HashMap<K, V, RandomState>;
|
||||
|
||||
// A random key iterator.
|
||||
#[derive(Clone, Copy)]
|
||||
struct RandomKeys {
|
||||
state: usize,
|
||||
}
|
||||
|
||||
impl RandomKeys {
|
||||
fn new() -> Self {
|
||||
RandomKeys { state: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for RandomKeys {
|
||||
type Item = usize;
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
// Add 1 then multiply by some 32 bit prime.
|
||||
self.state = self.state.wrapping_add(1).wrapping_mul(3_787_392_781);
|
||||
Some(self.state)
|
||||
}
|
||||
}
|
||||
|
||||
// Just an arbitrary side effect to make the maps not shortcircuit to the non-dropping path
|
||||
// when dropping maps/entries (most real world usages likely have drop in the key or value)
|
||||
lazy_static::lazy_static! {
|
||||
static ref SIDE_EFFECT: AtomicUsize = AtomicUsize::new(0);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DropType(usize);
|
||||
impl Drop for DropType {
|
||||
fn drop(&mut self) {
|
||||
SIDE_EFFECT.fetch_add(self.0, atomic::Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bench_suite {
|
||||
($bench_macro:ident, $bench_ahash_serial:ident, $bench_std_serial:ident,
|
||||
$bench_ahash_highbits:ident, $bench_std_highbits:ident,
|
||||
$bench_ahash_random:ident, $bench_std_random:ident) => {
|
||||
$bench_macro!($bench_ahash_serial, AHashMap, 0..);
|
||||
$bench_macro!($bench_std_serial, StdHashMap, 0..);
|
||||
$bench_macro!(
|
||||
$bench_ahash_highbits,
|
||||
AHashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!(
|
||||
$bench_std_highbits,
|
||||
StdHashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!($bench_ahash_random, AHashMap, RandomKeys::new());
|
||||
$bench_macro!($bench_std_random, StdHashMap, RandomKeys::new());
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bench_insert {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::with_capacity_and_hasher(SIZE, Default::default());
|
||||
b.iter(|| {
|
||||
m.clear();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, (DropType(i), [i; 20]));
|
||||
}
|
||||
black_box(&mut m);
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_insert,
|
||||
insert_ahash_serial,
|
||||
insert_std_serial,
|
||||
insert_ahash_highbits,
|
||||
insert_std_highbits,
|
||||
insert_ahash_random,
|
||||
insert_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_grow_insert {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut m = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
black_box(&mut m);
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_grow_insert,
|
||||
grow_insert_ahash_serial,
|
||||
grow_insert_std_serial,
|
||||
grow_insert_ahash_highbits,
|
||||
grow_insert_std_highbits,
|
||||
grow_insert_ahash_random,
|
||||
grow_insert_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_insert_erase {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut base = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
base.insert(i, DropType(i));
|
||||
}
|
||||
let skip = $keydist.skip(SIZE);
|
||||
b.iter(|| {
|
||||
let mut m = base.clone();
|
||||
let mut add_iter = skip.clone();
|
||||
let mut remove_iter = $keydist;
|
||||
// While keeping the size constant,
|
||||
// replace the first keydist with the second.
|
||||
for (add, remove) in (&mut add_iter).zip(&mut remove_iter).take(SIZE) {
|
||||
m.insert(add, DropType(add));
|
||||
black_box(m.remove(&remove));
|
||||
}
|
||||
black_box(m);
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_insert_erase,
|
||||
insert_erase_ahash_serial,
|
||||
insert_erase_std_serial,
|
||||
insert_erase_ahash_highbits,
|
||||
insert_erase_std_highbits,
|
||||
insert_erase_ahash_random,
|
||||
insert_erase_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
for i in $keydist.take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in $keydist.take(SIZE) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
});
|
||||
eprintln!("{}", SIDE_EFFECT.load(atomic::Ordering::SeqCst));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_lookup,
|
||||
lookup_ahash_serial,
|
||||
lookup_std_serial,
|
||||
lookup_ahash_highbits,
|
||||
lookup_std_highbits,
|
||||
lookup_ahash_random,
|
||||
lookup_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup_fail {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
let mut iter = $keydist;
|
||||
for i in (&mut iter).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in (&mut iter).take(SIZE) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_lookup_fail,
|
||||
lookup_fail_ahash_serial,
|
||||
lookup_fail_std_serial,
|
||||
lookup_fail_ahash_highbits,
|
||||
lookup_fail_std_highbits,
|
||||
lookup_fail_ahash_random,
|
||||
lookup_fail_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_iter {
|
||||
($name:ident, $maptype:ident, $keydist:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
let mut m = $maptype::default();
|
||||
for i in ($keydist).take(SIZE) {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in &m {
|
||||
black_box(i);
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite!(
|
||||
bench_iter,
|
||||
iter_ahash_serial,
|
||||
iter_std_serial,
|
||||
iter_ahash_highbits,
|
||||
iter_std_highbits,
|
||||
iter_ahash_random,
|
||||
iter_std_random
|
||||
);
|
||||
|
||||
#[bench]
|
||||
fn clone_small(b: &mut Bencher) {
|
||||
let mut m = HashMap::new();
|
||||
for i in 0..10 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
black_box(m.clone());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_from_small(b: &mut Bencher) {
|
||||
let mut m = HashMap::new();
|
||||
let mut m2 = HashMap::new();
|
||||
for i in 0..10 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
m2.clone_from(&m);
|
||||
black_box(&mut m2);
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_large(b: &mut Bencher) {
|
||||
let mut m = HashMap::new();
|
||||
for i in 0..1000 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
black_box(m.clone());
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn clone_from_large(b: &mut Bencher) {
|
||||
let mut m = HashMap::new();
|
||||
let mut m2 = HashMap::new();
|
||||
for i in 0..1000 {
|
||||
m.insert(i, DropType(i));
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
m2.clone_from(&m);
|
||||
black_box(&mut m2);
|
||||
})
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn rehash_in_place(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
let mut set = HashSet::new();
|
||||
|
||||
// Each loop triggers one rehash
|
||||
for _ in 0..10 {
|
||||
for i in 0..224 {
|
||||
set.insert(i);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
set.capacity(),
|
||||
224,
|
||||
"The set must be at or close to capacity to trigger a re hashing"
|
||||
);
|
||||
|
||||
for i in 100..1400 {
|
||||
set.remove(&(i - 100));
|
||||
set.insert(i);
|
||||
}
|
||||
set.clear();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//! Compare `insert` and `insert_unique_unchecked` operations performance.
|
||||
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn insert(b: &mut Bencher) {
|
||||
let keys: Vec<String> = (0..1000).map(|i| format!("xxxx{}yyyy", i)).collect();
|
||||
b.iter(|| {
|
||||
let mut m = HashMap::with_capacity(1000);
|
||||
for k in &keys {
|
||||
m.insert(k, k);
|
||||
}
|
||||
m
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn insert_unique_unchecked(b: &mut Bencher) {
|
||||
let keys: Vec<String> = (0..1000).map(|i| format!("xxxx{}yyyy", i)).collect();
|
||||
b.iter(|| {
|
||||
let mut m = HashMap::with_capacity(1000);
|
||||
for k in &keys {
|
||||
m.insert_unique_unchecked(k, k);
|
||||
}
|
||||
m
|
||||
});
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
doc-valid-idents = [ "CppCon", "SwissTable", "SipHash", "HashDoS" ]
|
|
@ -0,0 +1 @@
|
|||
{"package":"Could not get crate checksum","files":{}}
|
|
@ -0,0 +1,51 @@
|
|||
rust-hashbrown (0.12.3-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.12.3 from crates.io using debcargo 2.5.0
|
||||
|
||||
-- Blair Noctis <n@sail.ng> Thu, 13 Oct 2022 22:10:16 -0400
|
||||
|
||||
rust-hashbrown (0.12.1-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.12.1 from crates.io using debcargo 2.5.0
|
||||
* drop patches already upstream.
|
||||
|
||||
-- Daniel Kahn Gillmor <dkg@fifthhorseman.net> Mon, 27 Jun 2022 17:02:36 -0400
|
||||
|
||||
rust-hashbrown (0.11.2-3) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.11.2 from crates.io using debcargo 2.5.0
|
||||
* Reinstate ahash feature (Closes: 987324)
|
||||
* Add patch to make testsuite work with rand 0.8.
|
||||
* Establish baseline for tests.
|
||||
|
||||
-- Peter Michael Green <plugwash@debian.org> Sun, 29 May 2022 16:45:06 +0000
|
||||
|
||||
rust-hashbrown (0.11.2-2) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.11.2 from crates.io using debcargo 2.5.0
|
||||
|
||||
-- Sylvestre Ledru <sylvestre@debian.org> Sat, 01 Jan 2022 21:21:30 +0100
|
||||
|
||||
rust-hashbrown (0.11.2-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.11.2 from crates.io using debcargo 2.5.0
|
||||
|
||||
-- Sylvestre Ledru <sylvestre@debian.org> Sat, 01 Jan 2022 21:11:11 +0100
|
||||
|
||||
rust-hashbrown (0.9.1-1) unstable; urgency=medium
|
||||
|
||||
* Team upload.
|
||||
* Package hashbrown 0.9.1 from crates.io using debcargo 2.4.4-alpha.0
|
||||
|
||||
-- Sylvestre Ledru <sylvestre@debian.org> Tue, 22 Dec 2020 16:57:52 +0100
|
||||
|
||||
rust-hashbrown (0.1.8-1) unstable; urgency=medium
|
||||
|
||||
* Package hashbrown 0.1.8 from crates.io using debcargo 2.2.9
|
||||
|
||||
-- Ximin Luo <infinity0@debian.org> Sat, 19 Jan 2019 12:49:57 -0800
|
|
@ -0,0 +1 @@
|
|||
12
|
|
@ -0,0 +1,88 @@
|
|||
Source: rust-hashbrown
|
||||
Section: rust
|
||||
Priority: optional
|
||||
Build-Depends: debhelper (>= 12),
|
||||
dh-cargo (>= 25),
|
||||
cargo:native <!nocheck>,
|
||||
rustc:native <!nocheck>,
|
||||
libstd-rust-dev <!nocheck>,
|
||||
librust-ahash-0.7-dev <!nocheck>
|
||||
Maintainer: Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
Uploaders:
|
||||
Ximin Luo <infinity0@debian.org>
|
||||
Standards-Version: 4.5.1
|
||||
Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/hashbrown]
|
||||
Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/hashbrown
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: librust-hashbrown-dev
|
||||
Architecture: any
|
||||
Multi-Arch: same
|
||||
Depends:
|
||||
${misc:Depends},
|
||||
librust-ahash-0.7+compile-time-rng-dev,
|
||||
librust-ahash-0.7-dev,
|
||||
librust-bumpalo-3+default-dev (>= 3.5.0-~~),
|
||||
librust-compiler-builtins-0.1+default-dev (>= 0.1.2-~~),
|
||||
librust-rayon-1+default-dev,
|
||||
librust-rustc-std-workspace-core-1+default-dev,
|
||||
librust-serde-1-dev (>= 1.0.25-~~)
|
||||
Provides:
|
||||
librust-hashbrown+ahash-dev (= ${binary:Version}),
|
||||
librust-hashbrown+ahash-compile-time-rng-dev (= ${binary:Version}),
|
||||
librust-hashbrown+bumpalo-dev (= ${binary:Version}),
|
||||
librust-hashbrown+compiler-builtins-dev (= ${binary:Version}),
|
||||
librust-hashbrown+core-dev (= ${binary:Version}),
|
||||
librust-hashbrown+default-dev (= ${binary:Version}),
|
||||
librust-hashbrown+inline-more-dev (= ${binary:Version}),
|
||||
librust-hashbrown+nightly-dev (= ${binary:Version}),
|
||||
librust-hashbrown+raw-dev (= ${binary:Version}),
|
||||
librust-hashbrown+rayon-dev (= ${binary:Version}),
|
||||
librust-hashbrown+rustc-dep-of-std-dev (= ${binary:Version}),
|
||||
librust-hashbrown+rustc-internal-api-dev (= ${binary:Version}),
|
||||
librust-hashbrown+serde-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+ahash-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+ahash-compile-time-rng-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+bumpalo-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+compiler-builtins-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+core-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+default-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+inline-more-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+nightly-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+raw-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+rayon-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+rustc-dep-of-std-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+rustc-internal-api-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0+serde-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+ahash-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+ahash-compile-time-rng-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+bumpalo-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+compiler-builtins-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+core-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+default-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+inline-more-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+nightly-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+raw-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+rayon-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+rustc-dep-of-std-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+rustc-internal-api-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12+serde-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+ahash-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+ahash-compile-time-rng-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+bumpalo-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+compiler-builtins-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+core-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+default-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+inline-more-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+nightly-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+raw-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+rayon-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+rustc-dep-of-std-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+rustc-internal-api-dev (= ${binary:Version}),
|
||||
librust-hashbrown-0.12.3+serde-dev (= ${binary:Version})
|
||||
Description: Rust port of Google's SwissTable hash map - Rust source code
|
||||
This package contains the source for the Rust hashbrown crate, packaged by
|
||||
debcargo for use with cargo and dh-cargo.
|
|
@ -0,0 +1,37 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: hashbrown
|
||||
Upstream-Contact: Amanieu d'Antras <amanieu@gmail.com>
|
||||
Source: https://github.com/rust-lang/hashbrown
|
||||
|
||||
Files: *
|
||||
Copyright: 2018-2019 Amanieu d'Antras <amanieu@gmail.com>
|
||||
License: MIT or Apache-2.0
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2019 Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
2019 Ximin Luo <infinity0@debian.org>
|
||||
License: MIT or Apache-2.0
|
||||
|
||||
License: Apache-2.0
|
||||
Debian systems provide the Apache 2.0 license in
|
||||
/usr/share/common-licenses/Apache-2.0
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,49 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: hashbrown
|
||||
Upstream-Contact: Amanieu d'Antras <amanieu@gmail.com>
|
||||
Source: https://github.com/rust-lang/hashbrown
|
||||
|
||||
Files: *
|
||||
Copyright: FIXME (overlay) UNKNOWN-YEARS Amanieu d'Antras <amanieu@gmail.com>
|
||||
License: MIT or Apache-2.0
|
||||
Comment:
|
||||
FIXME (overlay): Since upstream copyright years are not available in
|
||||
Cargo.toml, they were extracted from the upstream Git repository. This may not
|
||||
be correct information so you should review and fix this before uploading to
|
||||
the archive.
|
||||
|
||||
Files: ./LICENSE-MIT
|
||||
Copyright: 2016 Amanieu d'Antras
|
||||
License: UNKNOWN-LICENSE; FIXME (overlay)
|
||||
Comment:
|
||||
FIXME (overlay): These notices are extracted from files. Please review them
|
||||
before uploading to the archive.
|
||||
|
||||
Files: debian/*
|
||||
Copyright:
|
||||
2019-2022 Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
|
||||
2019-2022 Ximin Luo <infinity0@debian.org>
|
||||
License: MIT or Apache-2.0
|
||||
|
||||
License: Apache-2.0
|
||||
Debian systems provide the Apache 2.0 license in
|
||||
/usr/share/common-licenses/Apache-2.0
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,20 @@
|
|||
overlay = "."
|
||||
uploaders = ["Ximin Luo <infinity0@debian.org>"]
|
||||
collapse_features = true
|
||||
|
||||
# tests fail without default features.
|
||||
[packages.lib]
|
||||
test_is_broken = true
|
||||
|
||||
# tests fail with "core" feature
|
||||
[packages."lib+core"]
|
||||
test_is_broken = true
|
||||
|
||||
# tests pass with default features
|
||||
[packages."lib+default"]
|
||||
test_is_broken = false
|
||||
|
||||
# tests pass with ahash feature.
|
||||
[packages."lib+ahash"]
|
||||
test_is_broken = false
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
--- hashbrown/Cargo.toml
|
||||
+++ hashbrown/Cargo.toml
|
||||
@@ -48,11 +48,6 @@
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
-[dependencies.alloc]
|
||||
-version = "1.0.0"
|
||||
-optional = true
|
||||
-package = "rustc-std-workspace-alloc"
|
||||
-
|
||||
[dependencies.bumpalo]
|
||||
version = "3.5.0"
|
||||
optional = true
|
||||
@@ -107,7 +102,6 @@
|
||||
"nightly",
|
||||
"core",
|
||||
"compiler_builtins",
|
||||
- "alloc",
|
||||
"rustc-internal-api",
|
||||
]
|
||||
rustc-internal-api = []
|
|
@ -0,0 +1 @@
|
|||
disable-alloc.diff
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/make -f
|
||||
%:
|
||||
dh $@ --buildsystem cargo
|
|
@ -0,0 +1 @@
|
|||
3.0 (quilt)
|
|
@ -0,0 +1,74 @@
|
|||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --all-features
|
||||
Features: test-name=rust-hashbrown:@
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features ahash
|
||||
Features: test-name=librust-hashbrown-dev:ahash
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features ahash-compile-time-rng
|
||||
Features: test-name=librust-hashbrown-dev:ahash-compile-time-rng
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features bumpalo
|
||||
Features: test-name=librust-hashbrown-dev:bumpalo
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features compiler_builtins
|
||||
Features: test-name=librust-hashbrown-dev:compiler_builtins
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features core
|
||||
Features: test-name=librust-hashbrown-dev:core
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets
|
||||
Features: test-name=librust-hashbrown-dev:default
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features inline-more
|
||||
Features: test-name=librust-hashbrown-dev:inline-more
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features nightly
|
||||
Features: test-name=librust-hashbrown-dev:nightly
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features raw
|
||||
Features: test-name=librust-hashbrown-dev:raw
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features rayon
|
||||
Features: test-name=librust-hashbrown-dev:rayon
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features rustc-dep-of-std
|
||||
Features: test-name=librust-hashbrown-dev:rustc-dep-of-std
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features rustc-internal-api
|
||||
Features: test-name=librust-hashbrown-dev:rustc-internal-api
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features --features serde
|
||||
Features: test-name=librust-hashbrown-dev:serde
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
||||
|
||||
Test-Command: /usr/share/cargo/bin/cargo-auto-test hashbrown 0.12.3 --all-targets --no-default-features
|
||||
Features: test-name=librust-hashbrown-dev:
|
||||
Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev (>= 0.3.1-~~), librust-fnv-1+default-dev (>= 1.0.7-~~), librust-lazy-static-1+default-dev (>= 1.4-~~), librust-rand-0.8+default-dev (>= 0.8.3-~~), librust-rand-0.8+small-rng-dev (>= 0.8.3-~~), librust-rayon-1+default-dev, librust-serde-test-1+default-dev, @
|
||||
Restrictions: allow-stderr, skip-not-installable, flaky
|
|
@ -0,0 +1,4 @@
|
|||
version=4
|
||||
opts=filenamemangle=s/.*\/(.*)\/download/hashbrown-$1\.tar\.gz/g,\
|
||||
uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/ \
|
||||
https://qa.debian.org/cgi-bin/fakeupstream.cgi?upstream=crates.io/hashbrown .*/crates/hashbrown/@ANY_VERSION@/download
|
|
@ -0,0 +1,4 @@
|
|||
#[cfg(feature = "rayon")]
|
||||
pub(crate) mod rayon;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
|
@ -0,0 +1,27 @@
|
|||
use alloc::collections::LinkedList;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
/// Helper for collecting parallel iterators to an intermediary
|
||||
#[allow(clippy::linkedlist)] // yes, we need linked list here for efficient appending!
|
||||
pub(super) fn collect<I: IntoParallelIterator>(iter: I) -> (LinkedList<Vec<I::Item>>, usize) {
|
||||
let list = iter
|
||||
.into_par_iter()
|
||||
.fold(Vec::new, |mut vec, elem| {
|
||||
vec.push(elem);
|
||||
vec
|
||||
})
|
||||
.map(|vec| {
|
||||
let mut list = LinkedList::new();
|
||||
list.push_back(vec);
|
||||
list
|
||||
})
|
||||
.reduce(LinkedList::new, |mut list1, mut list2| {
|
||||
list1.append(&mut list2);
|
||||
list1
|
||||
});
|
||||
|
||||
let len = list.iter().map(Vec::len).sum();
|
||||
(list, len)
|
||||
}
|
|
@ -0,0 +1,734 @@
|
|||
//! Rayon extensions for `HashMap`.
|
||||
|
||||
use super::raw::{RawIntoParIter, RawParDrain, RawParIter};
|
||||
use crate::hash_map::HashMap;
|
||||
use crate::raw::{Allocator, Global};
|
||||
use core::fmt;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::marker::PhantomData;
|
||||
use rayon::iter::plumbing::UnindexedConsumer;
|
||||
use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator};
|
||||
|
||||
/// Parallel iterator over shared references to entries in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_iter`] method on [`HashMap`]
|
||||
/// (provided by the [`IntoParallelRefIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_iter`]: /hashbrown/struct.HashMap.html#method.par_iter
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
/// [`IntoParallelRefIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelRefIterator.html
|
||||
pub struct ParIter<'a, K, V> {
|
||||
inner: RawParIter<(K, V)>,
|
||||
marker: PhantomData<(&'a K, &'a V)>,
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Sync> ParallelIterator for ParIter<'a, K, V> {
|
||||
type Item = (&'a K, &'a V);
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner
|
||||
.map(|x| unsafe {
|
||||
let r = x.as_ref();
|
||||
(&r.0, &r.1)
|
||||
})
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Clone for ParIter<'_, K, V> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Eq + Hash, V: fmt::Debug> fmt::Debug for ParIter<'_, K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let iter = unsafe { self.inner.iter() }.map(|x| unsafe {
|
||||
let r = x.as_ref();
|
||||
(&r.0, &r.1)
|
||||
});
|
||||
f.debug_list().entries(iter).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to keys in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_keys`] method on [`HashMap`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_keys`]: /hashbrown/struct.HashMap.html#method.par_keys
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
pub struct ParKeys<'a, K, V> {
|
||||
inner: RawParIter<(K, V)>,
|
||||
marker: PhantomData<(&'a K, &'a V)>,
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Sync> ParallelIterator for ParKeys<'a, K, V> {
|
||||
type Item = &'a K;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner
|
||||
.map(|x| unsafe { &x.as_ref().0 })
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Clone for ParKeys<'_, K, V> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Eq + Hash, V> fmt::Debug for ParKeys<'_, K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let iter = unsafe { self.inner.iter() }.map(|x| unsafe { &x.as_ref().0 });
|
||||
f.debug_list().entries(iter).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to values in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_values`] method on [`HashMap`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_values`]: /hashbrown/struct.HashMap.html#method.par_values
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
pub struct ParValues<'a, K, V> {
|
||||
inner: RawParIter<(K, V)>,
|
||||
marker: PhantomData<(&'a K, &'a V)>,
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Sync> ParallelIterator for ParValues<'a, K, V> {
|
||||
type Item = &'a V;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner
|
||||
.map(|x| unsafe { &x.as_ref().1 })
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Clone for ParValues<'_, K, V> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V: fmt::Debug> fmt::Debug for ParValues<'_, K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let iter = unsafe { self.inner.iter() }.map(|x| unsafe { &x.as_ref().1 });
|
||||
f.debug_list().entries(iter).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over mutable references to entries in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_iter_mut`] method on [`HashMap`]
|
||||
/// (provided by the [`IntoParallelRefMutIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_iter_mut`]: /hashbrown/struct.HashMap.html#method.par_iter_mut
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
/// [`IntoParallelRefMutIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelRefMutIterator.html
|
||||
pub struct ParIterMut<'a, K, V> {
|
||||
inner: RawParIter<(K, V)>,
|
||||
marker: PhantomData<(&'a K, &'a mut V)>,
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Send> ParallelIterator for ParIterMut<'a, K, V> {
|
||||
type Item = (&'a K, &'a mut V);
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner
|
||||
.map(|x| unsafe {
|
||||
let r = x.as_mut();
|
||||
(&r.0, &mut r.1)
|
||||
})
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Eq + Hash, V: fmt::Debug> fmt::Debug for ParIterMut<'_, K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParIter {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over mutable references to values in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_values_mut`] method on [`HashMap`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_values_mut`]: /hashbrown/struct.HashMap.html#method.par_values_mut
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
pub struct ParValuesMut<'a, K, V> {
|
||||
inner: RawParIter<(K, V)>,
|
||||
marker: PhantomData<(&'a K, &'a mut V)>,
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Send> ParallelIterator for ParValuesMut<'a, K, V> {
|
||||
type Item = &'a mut V;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner
|
||||
.map(|x| unsafe { &mut x.as_mut().1 })
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Eq + Hash, V: fmt::Debug> fmt::Debug for ParValuesMut<'_, K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParValues {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over entries of a consumed map.
|
||||
///
|
||||
/// This iterator is created by the [`into_par_iter`] method on [`HashMap`]
|
||||
/// (provided by the [`IntoParallelIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`into_par_iter`]: /hashbrown/struct.HashMap.html#method.into_par_iter
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
/// [`IntoParallelIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelIterator.html
|
||||
pub struct IntoParIter<K, V, A: Allocator + Clone = Global> {
|
||||
inner: RawIntoParIter<(K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, A: Allocator + Clone + Send> ParallelIterator for IntoParIter<K, V, A> {
|
||||
type Item = (K, V);
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Eq + Hash, V: fmt::Debug, A: Allocator + Clone> fmt::Debug
|
||||
for IntoParIter<K, V, A>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParIter {
|
||||
inner: unsafe { self.inner.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel draining iterator over entries of a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_drain`] method on [`HashMap`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_drain`]: /hashbrown/struct.HashMap.html#method.par_drain
|
||||
/// [`HashMap`]: /hashbrown/struct.HashMap.html
|
||||
pub struct ParDrain<'a, K, V, A: Allocator + Clone = Global> {
|
||||
inner: RawParDrain<'a, (K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, A: Allocator + Clone + Sync> ParallelIterator for ParDrain<'_, K, V, A> {
|
||||
type Item = (K, V);
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: fmt::Debug + Eq + Hash, V: fmt::Debug, A: Allocator + Clone> fmt::Debug
|
||||
for ParDrain<'_, K, V, A>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParIter {
|
||||
inner: unsafe { self.inner.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Sync, V: Sync, S, A: Allocator + Clone> HashMap<K, V, S, A> {
|
||||
/// Visits (potentially in parallel) immutably borrowed keys in an arbitrary order.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_keys(&self) -> ParKeys<'_, K, V> {
|
||||
ParKeys {
|
||||
inner: unsafe { self.table.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Visits (potentially in parallel) immutably borrowed values in an arbitrary order.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_values(&self) -> ParValues<'_, K, V> {
|
||||
ParValues {
|
||||
inner: unsafe { self.table.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, S, A: Allocator + Clone> HashMap<K, V, S, A> {
|
||||
/// Visits (potentially in parallel) mutably borrowed values in an arbitrary order.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_values_mut(&mut self) -> ParValuesMut<'_, K, V> {
|
||||
ParValuesMut {
|
||||
inner: unsafe { self.table.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes (potentially in parallel) all values in an arbitrary order,
|
||||
/// while preserving the map's allocated memory for reuse.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_drain(&mut self) -> ParDrain<'_, K, V, A> {
|
||||
ParDrain {
|
||||
inner: self.table.par_drain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, S, A> HashMap<K, V, S, A>
|
||||
where
|
||||
K: Eq + Hash + Sync,
|
||||
V: PartialEq + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
/// Returns `true` if the map is equal to another,
|
||||
/// i.e. both maps contain the same keys mapped to the same values.
|
||||
///
|
||||
/// This method runs in a potentially parallel fashion.
|
||||
pub fn par_eq(&self, other: &Self) -> bool {
|
||||
self.len() == other.len()
|
||||
&& self
|
||||
.into_par_iter()
|
||||
.all(|(key, value)| other.get(key).map_or(false, |v| *value == *v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, S, A: Allocator + Clone + Send> IntoParallelIterator
|
||||
for HashMap<K, V, S, A>
|
||||
{
|
||||
type Item = (K, V);
|
||||
type Iter = IntoParIter<K, V, A>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
IntoParIter {
|
||||
inner: self.table.into_par_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Sync, S, A: Allocator + Clone> IntoParallelIterator
|
||||
for &'a HashMap<K, V, S, A>
|
||||
{
|
||||
type Item = (&'a K, &'a V);
|
||||
type Iter = ParIter<'a, K, V>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
ParIter {
|
||||
inner: unsafe { self.table.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Sync, V: Send, S, A: Allocator + Clone> IntoParallelIterator
|
||||
for &'a mut HashMap<K, V, S, A>
|
||||
{
|
||||
type Item = (&'a K, &'a mut V);
|
||||
type Iter = ParIterMut<'a, K, V>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
ParIterMut {
|
||||
inner: unsafe { self.table.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect (key, value) pairs from a parallel iterator into a
|
||||
/// hashmap. If multiple pairs correspond to the same key, then the
|
||||
/// ones produced earlier in the parallel iterator will be
|
||||
/// overwritten, just as with a sequential iterator.
|
||||
impl<K, V, S> FromParallelIterator<(K, V)> for HashMap<K, V, S, Global>
|
||||
where
|
||||
K: Eq + Hash + Send,
|
||||
V: Send,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
fn from_par_iter<P>(par_iter: P) -> Self
|
||||
where
|
||||
P: IntoParallelIterator<Item = (K, V)>,
|
||||
{
|
||||
let mut map = HashMap::default();
|
||||
map.par_extend(par_iter);
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a hash map with items from a parallel iterator.
|
||||
impl<K, V, S, A> ParallelExtend<(K, V)> for HashMap<K, V, S, A>
|
||||
where
|
||||
K: Eq + Hash + Send,
|
||||
V: Send,
|
||||
S: BuildHasher,
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
fn par_extend<I>(&mut self, par_iter: I)
|
||||
where
|
||||
I: IntoParallelIterator<Item = (K, V)>,
|
||||
{
|
||||
extend(self, par_iter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a hash map with copied items from a parallel iterator.
|
||||
impl<'a, K, V, S, A> ParallelExtend<(&'a K, &'a V)> for HashMap<K, V, S, A>
|
||||
where
|
||||
K: Copy + Eq + Hash + Sync,
|
||||
V: Copy + Sync,
|
||||
S: BuildHasher,
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
fn par_extend<I>(&mut self, par_iter: I)
|
||||
where
|
||||
I: IntoParallelIterator<Item = (&'a K, &'a V)>,
|
||||
{
|
||||
extend(self, par_iter);
|
||||
}
|
||||
}
|
||||
|
||||
// This is equal to the normal `HashMap` -- no custom advantage.
|
||||
fn extend<K, V, S, A, I>(map: &mut HashMap<K, V, S, A>, par_iter: I)
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: BuildHasher,
|
||||
I: IntoParallelIterator,
|
||||
A: Allocator + Clone,
|
||||
HashMap<K, V, S, A>: Extend<I::Item>,
|
||||
{
|
||||
let (list, len) = super::helpers::collect(par_iter);
|
||||
|
||||
// Keys may be already present or show multiple times in the iterator.
|
||||
// Reserve the entire length if the map is empty.
|
||||
// Otherwise reserve half the length (rounded up), so the map
|
||||
// will only resize twice in the worst case.
|
||||
let reserve = if map.is_empty() { len } else { (len + 1) / 2 };
|
||||
map.reserve(reserve);
|
||||
for vec in list {
|
||||
map.extend(vec);
|
||||
}
|
||||
}
|
||||
|
||||
#[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_into_iter_drops() {
|
||||
let key = AtomicUsize::new(0);
|
||||
let value = AtomicUsize::new(0);
|
||||
|
||||
let hm = {
|
||||
let mut hm = HashMap::new();
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 0);
|
||||
|
||||
for i in 0..100 {
|
||||
let d1 = Dropable::new(i, &key);
|
||||
let d2 = Dropable::new(i + 100, &value);
|
||||
hm.insert(d1, d2);
|
||||
}
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
hm
|
||||
};
|
||||
|
||||
// By the way, ensure that cloning doesn't screw up the dropping.
|
||||
drop(hm.clone());
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
// Ensure that dropping the iterator does not leak anything.
|
||||
drop(hm.clone().into_par_iter());
|
||||
|
||||
{
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
// retain only half
|
||||
let _v: Vec<_> = hm
|
||||
.into_par_iter()
|
||||
.filter(|&(ref key, _)| key.k < 50)
|
||||
.collect();
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 50);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 50);
|
||||
};
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drain_drops() {
|
||||
let key = AtomicUsize::new(0);
|
||||
let value = AtomicUsize::new(0);
|
||||
|
||||
let mut hm = {
|
||||
let mut hm = HashMap::new();
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 0);
|
||||
|
||||
for i in 0..100 {
|
||||
let d1 = Dropable::new(i, &key);
|
||||
let d2 = Dropable::new(i + 100, &value);
|
||||
hm.insert(d1, d2);
|
||||
}
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
hm
|
||||
};
|
||||
|
||||
// By the way, ensure that cloning doesn't screw up the dropping.
|
||||
drop(hm.clone());
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
// Ensure that dropping the drain iterator does not leak anything.
|
||||
drop(hm.clone().par_drain());
|
||||
|
||||
{
|
||||
assert_eq!(key.load(Ordering::Relaxed), 100);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 100);
|
||||
|
||||
// retain only half
|
||||
let _v: Vec<_> = hm.drain().filter(|&(ref key, _)| key.k < 50).collect();
|
||||
assert!(hm.is_empty());
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 50);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 50);
|
||||
};
|
||||
|
||||
assert_eq!(key.load(Ordering::Relaxed), 0);
|
||||
assert_eq!(value.load(Ordering::Relaxed), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_iter() {
|
||||
let mut m: HashMap<isize, bool> = HashMap::new();
|
||||
assert_eq!(m.par_drain().count(), 0);
|
||||
assert_eq!(m.par_keys().count(), 0);
|
||||
assert_eq!(m.par_values().count(), 0);
|
||||
assert_eq!(m.par_values_mut().count(), 0);
|
||||
assert_eq!(m.par_iter().count(), 0);
|
||||
assert_eq!(m.par_iter_mut().count(), 0);
|
||||
assert_eq!(m.len(), 0);
|
||||
assert!(m.is_empty());
|
||||
assert_eq!(m.into_par_iter().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterate() {
|
||||
let mut m = HashMap::with_capacity(4);
|
||||
for i in 0..32 {
|
||||
assert!(m.insert(i, i * 2).is_none());
|
||||
}
|
||||
assert_eq!(m.len(), 32);
|
||||
|
||||
let observed = AtomicUsize::new(0);
|
||||
|
||||
m.par_iter().for_each(|(k, v)| {
|
||||
assert_eq!(*v, *k * 2);
|
||||
observed.fetch_or(1 << *k, Ordering::Relaxed);
|
||||
});
|
||||
assert_eq!(observed.into_inner(), 0xFFFF_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keys() {
|
||||
let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')];
|
||||
let map: HashMap<_, _> = vec.into_par_iter().collect();
|
||||
let keys: Vec<_> = map.par_keys().cloned().collect();
|
||||
assert_eq!(keys.len(), 3);
|
||||
assert!(keys.contains(&1));
|
||||
assert!(keys.contains(&2));
|
||||
assert!(keys.contains(&3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values() {
|
||||
let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')];
|
||||
let map: HashMap<_, _> = vec.into_par_iter().collect();
|
||||
let values: Vec<_> = map.par_values().cloned().collect();
|
||||
assert_eq!(values.len(), 3);
|
||||
assert!(values.contains(&'a'));
|
||||
assert!(values.contains(&'b'));
|
||||
assert!(values.contains(&'c'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values_mut() {
|
||||
let vec = vec![(1, 1), (2, 2), (3, 3)];
|
||||
let mut map: HashMap<_, _> = vec.into_par_iter().collect();
|
||||
map.par_values_mut().for_each(|value| *value *= 2);
|
||||
let values: Vec<_> = map.par_values().cloned().collect();
|
||||
assert_eq!(values.len(), 3);
|
||||
assert!(values.contains(&2));
|
||||
assert!(values.contains(&4));
|
||||
assert!(values.contains(&6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq() {
|
||||
let mut m1 = HashMap::new();
|
||||
m1.insert(1, 2);
|
||||
m1.insert(2, 3);
|
||||
m1.insert(3, 4);
|
||||
|
||||
let mut m2 = HashMap::new();
|
||||
m2.insert(1, 2);
|
||||
m2.insert(2, 3);
|
||||
|
||||
assert!(!m1.par_eq(&m2));
|
||||
|
||||
m2.insert(3, 4);
|
||||
|
||||
assert!(m1.par_eq(&m2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_iter() {
|
||||
let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)];
|
||||
|
||||
let map: HashMap<_, _> = xs.par_iter().cloned().collect();
|
||||
|
||||
for &(k, v) in &xs {
|
||||
assert_eq!(map.get(&k), Some(&v));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_ref() {
|
||||
let mut a = HashMap::new();
|
||||
a.insert(1, "one");
|
||||
let mut b = HashMap::new();
|
||||
b.insert(2, "two");
|
||||
b.insert(3, "three");
|
||||
|
||||
a.par_extend(&b);
|
||||
|
||||
assert_eq!(a.len(), 3);
|
||||
assert_eq!(a[&1], "one");
|
||||
assert_eq!(a[&2], "two");
|
||||
assert_eq!(a[&3], "three");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
mod helpers;
|
||||
pub(crate) mod map;
|
||||
pub(crate) mod raw;
|
||||
pub(crate) mod set;
|
|
@ -0,0 +1,231 @@
|
|||
use crate::raw::Bucket;
|
||||
use crate::raw::{Allocator, Global, RawIter, RawIterRange, RawTable};
|
||||
use crate::scopeguard::guard;
|
||||
use alloc::alloc::dealloc;
|
||||
use core::marker::PhantomData;
|
||||
use core::mem;
|
||||
use core::ptr::NonNull;
|
||||
use rayon::iter::{
|
||||
plumbing::{self, Folder, UnindexedConsumer, UnindexedProducer},
|
||||
ParallelIterator,
|
||||
};
|
||||
|
||||
/// Parallel iterator which returns a raw pointer to every full bucket in the table.
|
||||
pub struct RawParIter<T> {
|
||||
iter: RawIterRange<T>,
|
||||
}
|
||||
|
||||
impl<T> RawParIter<T> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub(super) unsafe fn iter(&self) -> RawIterRange<T> {
|
||||
self.iter.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for RawParIter<T> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
iter: self.iter.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<RawIter<T>> for RawParIter<T> {
|
||||
fn from(it: RawIter<T>) -> Self {
|
||||
RawParIter { iter: it.iter }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ParallelIterator for RawParIter<T> {
|
||||
type Item = Bucket<T>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
let producer = ParIterProducer { iter: self.iter };
|
||||
plumbing::bridge_unindexed(producer, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Producer which returns a `Bucket<T>` for every element.
|
||||
struct ParIterProducer<T> {
|
||||
iter: RawIterRange<T>,
|
||||
}
|
||||
|
||||
impl<T> UnindexedProducer for ParIterProducer<T> {
|
||||
type Item = Bucket<T>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn split(self) -> (Self, Option<Self>) {
|
||||
let (left, right) = self.iter.split();
|
||||
let left = ParIterProducer { iter: left };
|
||||
let right = right.map(|right| ParIterProducer { iter: right });
|
||||
(left, right)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn fold_with<F>(self, folder: F) -> F
|
||||
where
|
||||
F: Folder<Self::Item>,
|
||||
{
|
||||
folder.consume_iter(self.iter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator which consumes a table and returns elements.
|
||||
pub struct RawIntoParIter<T, A: Allocator + Clone = Global> {
|
||||
table: RawTable<T, A>,
|
||||
}
|
||||
|
||||
impl<T, A: Allocator + Clone> RawIntoParIter<T, A> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub(super) unsafe fn par_iter(&self) -> RawParIter<T> {
|
||||
self.table.par_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Clone + Send> ParallelIterator for RawIntoParIter<T, A> {
|
||||
type Item = T;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
let iter = unsafe { self.table.iter().iter };
|
||||
let _guard = guard(self.table.into_allocation(), |alloc| {
|
||||
if let Some((ptr, layout)) = *alloc {
|
||||
unsafe {
|
||||
dealloc(ptr.as_ptr(), layout);
|
||||
}
|
||||
}
|
||||
});
|
||||
let producer = ParDrainProducer { iter };
|
||||
plumbing::bridge_unindexed(producer, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator which consumes elements without freeing the table storage.
|
||||
pub struct RawParDrain<'a, T, A: Allocator + Clone = Global> {
|
||||
// We don't use a &'a mut RawTable<T> because we want RawParDrain to be
|
||||
// covariant over T.
|
||||
table: NonNull<RawTable<T, A>>,
|
||||
marker: PhantomData<&'a RawTable<T, A>>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send, A: Allocator + Clone> Send for RawParDrain<'_, T, A> {}
|
||||
|
||||
impl<T, A: Allocator + Clone> RawParDrain<'_, T, A> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub(super) unsafe fn par_iter(&self) -> RawParIter<T> {
|
||||
self.table.as_ref().par_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Clone> ParallelIterator for RawParDrain<'_, T, A> {
|
||||
type Item = T;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
let _guard = guard(self.table, |table| unsafe {
|
||||
table.as_mut().clear_no_drop();
|
||||
});
|
||||
let iter = unsafe { self.table.as_ref().iter().iter };
|
||||
mem::forget(self);
|
||||
let producer = ParDrainProducer { iter };
|
||||
plumbing::bridge_unindexed(producer, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A: Allocator + Clone> Drop for RawParDrain<'_, T, A> {
|
||||
fn drop(&mut self) {
|
||||
// If drive_unindexed is not called then simply clear the table.
|
||||
unsafe {
|
||||
self.table.as_mut().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Producer which will consume all elements in the range, even if it is dropped
|
||||
/// halfway through.
|
||||
struct ParDrainProducer<T> {
|
||||
iter: RawIterRange<T>,
|
||||
}
|
||||
|
||||
impl<T: Send> UnindexedProducer for ParDrainProducer<T> {
|
||||
type Item = T;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn split(self) -> (Self, Option<Self>) {
|
||||
let (left, right) = self.iter.clone().split();
|
||||
mem::forget(self);
|
||||
let left = ParDrainProducer { iter: left };
|
||||
let right = right.map(|right| ParDrainProducer { iter: right });
|
||||
(left, right)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn fold_with<F>(mut self, mut folder: F) -> F
|
||||
where
|
||||
F: Folder<Self::Item>,
|
||||
{
|
||||
// Make sure to modify the iterator in-place so that any remaining
|
||||
// elements are processed in our Drop impl.
|
||||
for item in &mut self.iter {
|
||||
folder = folder.consume(unsafe { item.read() });
|
||||
if folder.full() {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
// If we processed all elements then we don't need to run the drop.
|
||||
mem::forget(self);
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for ParDrainProducer<T> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn drop(&mut self) {
|
||||
// Drop all remaining elements
|
||||
if mem::needs_drop::<T>() {
|
||||
for item in &mut self.iter {
|
||||
unsafe {
|
||||
item.drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, A: Allocator + Clone> RawTable<T, A> {
|
||||
/// Returns a parallel iterator over the elements in a `RawTable`.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub unsafe fn par_iter(&self) -> RawParIter<T> {
|
||||
RawParIter {
|
||||
iter: self.iter().iter,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over the elements in a `RawTable`.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn into_par_iter(self) -> RawIntoParIter<T, A> {
|
||||
RawIntoParIter { table: self }
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator which consumes all elements of a `RawTable`
|
||||
/// without freeing its memory allocation.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_drain(&mut self) -> RawParDrain<'_, T, A> {
|
||||
RawParDrain {
|
||||
table: NonNull::from(self),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,659 @@
|
|||
//! Rayon extensions for `HashSet`.
|
||||
|
||||
use super::map;
|
||||
use crate::hash_set::HashSet;
|
||||
use crate::raw::{Allocator, Global};
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use rayon::iter::plumbing::UnindexedConsumer;
|
||||
use rayon::iter::{FromParallelIterator, IntoParallelIterator, ParallelExtend, ParallelIterator};
|
||||
|
||||
/// Parallel iterator over elements of a consumed set.
|
||||
///
|
||||
/// This iterator is created by the [`into_par_iter`] method on [`HashSet`]
|
||||
/// (provided by the [`IntoParallelIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`into_par_iter`]: /hashbrown/struct.HashSet.html#method.into_par_iter
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
/// [`IntoParallelIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelIterator.html
|
||||
pub struct IntoParIter<T, A: Allocator + Clone = Global> {
|
||||
inner: map::IntoParIter<T, (), A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Clone + Send> ParallelIterator for IntoParIter<T, A> {
|
||||
type Item = T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner.map(|(k, _)| k).drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel draining iterator over entries of a set.
|
||||
///
|
||||
/// This iterator is created by the [`par_drain`] method on [`HashSet`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_drain`]: /hashbrown/struct.HashSet.html#method.par_drain
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
pub struct ParDrain<'a, T, A: Allocator + Clone = Global> {
|
||||
inner: map::ParDrain<'a, T, (), A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Clone + Send + Sync> ParallelIterator for ParDrain<'_, T, A> {
|
||||
type Item = T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner.map(|(k, _)| k).drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to elements in a set.
|
||||
///
|
||||
/// This iterator is created by the [`par_iter`] method on [`HashSet`]
|
||||
/// (provided by the [`IntoParallelRefIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_iter`]: /hashbrown/struct.HashSet.html#method.par_iter
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
/// [`IntoParallelRefIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelRefIterator.html
|
||||
pub struct ParIter<'a, T> {
|
||||
inner: map::ParKeys<'a, T, ()>,
|
||||
}
|
||||
|
||||
impl<'a, T: Sync> ParallelIterator for ParIter<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.inner.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to elements in the difference of
|
||||
/// sets.
|
||||
///
|
||||
/// This iterator is created by the [`par_difference`] method on [`HashSet`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_difference`]: /hashbrown/struct.HashSet.html#method.par_difference
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
pub struct ParDifference<'a, T, S, A: Allocator + Clone = Global> {
|
||||
a: &'a HashSet<T, S, A>,
|
||||
b: &'a HashSet<T, S, A>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> ParallelIterator for ParDifference<'a, T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.a
|
||||
.into_par_iter()
|
||||
.filter(|&x| !self.b.contains(x))
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to elements in the symmetric
|
||||
/// difference of sets.
|
||||
///
|
||||
/// This iterator is created by the [`par_symmetric_difference`] method on
|
||||
/// [`HashSet`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_symmetric_difference`]: /hashbrown/struct.HashSet.html#method.par_symmetric_difference
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
pub struct ParSymmetricDifference<'a, T, S, A: Allocator + Clone = Global> {
|
||||
a: &'a HashSet<T, S, A>,
|
||||
b: &'a HashSet<T, S, A>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> ParallelIterator for ParSymmetricDifference<'a, T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.a
|
||||
.par_difference(self.b)
|
||||
.chain(self.b.par_difference(self.a))
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to elements in the intersection of
|
||||
/// sets.
|
||||
///
|
||||
/// This iterator is created by the [`par_intersection`] method on [`HashSet`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_intersection`]: /hashbrown/struct.HashSet.html#method.par_intersection
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
pub struct ParIntersection<'a, T, S, A: Allocator + Clone = Global> {
|
||||
a: &'a HashSet<T, S, A>,
|
||||
b: &'a HashSet<T, S, A>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> ParallelIterator for ParIntersection<'a, T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
self.a
|
||||
.into_par_iter()
|
||||
.filter(|&x| self.b.contains(x))
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parallel iterator over shared references to elements in the union of sets.
|
||||
///
|
||||
/// This iterator is created by the [`par_union`] method on [`HashSet`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_union`]: /hashbrown/struct.HashSet.html#method.par_union
|
||||
/// [`HashSet`]: /hashbrown/struct.HashSet.html
|
||||
pub struct ParUnion<'a, T, S, A: Allocator + Clone = Global> {
|
||||
a: &'a HashSet<T, S, A>,
|
||||
b: &'a HashSet<T, S, A>,
|
||||
}
|
||||
|
||||
impl<'a, T, S, A> ParallelIterator for ParUnion<'a, T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
type Item = &'a T;
|
||||
|
||||
fn drive_unindexed<C>(self, consumer: C) -> C::Result
|
||||
where
|
||||
C: UnindexedConsumer<Self::Item>,
|
||||
{
|
||||
// We'll iterate one set in full, and only the remaining difference from the other.
|
||||
// Use the smaller set for the difference in order to reduce hash lookups.
|
||||
let (smaller, larger) = if self.a.len() <= self.b.len() {
|
||||
(self.a, self.b)
|
||||
} else {
|
||||
(self.b, self.a)
|
||||
};
|
||||
larger
|
||||
.into_par_iter()
|
||||
.chain(smaller.par_difference(larger))
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, A> HashSet<T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Sync,
|
||||
S: BuildHasher + Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
/// Visits (potentially in parallel) the values representing the union,
|
||||
/// i.e. all the values in `self` or `other`, without duplicates.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_union<'a>(&'a self, other: &'a Self) -> ParUnion<'a, T, S, A> {
|
||||
ParUnion { a: self, b: other }
|
||||
}
|
||||
|
||||
/// Visits (potentially in parallel) the values representing the difference,
|
||||
/// i.e. the values that are in `self` but not in `other`.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_difference<'a>(&'a self, other: &'a Self) -> ParDifference<'a, T, S, A> {
|
||||
ParDifference { a: self, b: other }
|
||||
}
|
||||
|
||||
/// Visits (potentially in parallel) the values representing the symmetric
|
||||
/// difference, i.e. the values that are in `self` or in `other` but not in both.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_symmetric_difference<'a>(
|
||||
&'a self,
|
||||
other: &'a Self,
|
||||
) -> ParSymmetricDifference<'a, T, S, A> {
|
||||
ParSymmetricDifference { a: self, b: other }
|
||||
}
|
||||
|
||||
/// Visits (potentially in parallel) the values representing the
|
||||
/// intersection, i.e. the values that are both in `self` and `other`.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_intersection<'a>(&'a self, other: &'a Self) -> ParIntersection<'a, T, S, A> {
|
||||
ParIntersection { a: self, b: other }
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` has no elements in common with `other`.
|
||||
/// This is equivalent to checking for an empty intersection.
|
||||
///
|
||||
/// This method runs in a potentially parallel fashion.
|
||||
pub fn par_is_disjoint(&self, other: &Self) -> bool {
|
||||
self.into_par_iter().all(|x| !other.contains(x))
|
||||
}
|
||||
|
||||
/// Returns `true` if the set is a subset of another,
|
||||
/// i.e. `other` contains at least all the values in `self`.
|
||||
///
|
||||
/// This method runs in a potentially parallel fashion.
|
||||
pub fn par_is_subset(&self, other: &Self) -> bool {
|
||||
if self.len() <= other.len() {
|
||||
self.into_par_iter().all(|x| other.contains(x))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the set is a superset of another,
|
||||
/// i.e. `self` contains at least all the values in `other`.
|
||||
///
|
||||
/// This method runs in a potentially parallel fashion.
|
||||
pub fn par_is_superset(&self, other: &Self) -> bool {
|
||||
other.par_is_subset(self)
|
||||
}
|
||||
|
||||
/// Returns `true` if the set is equal to another,
|
||||
/// i.e. both sets contain the same values.
|
||||
///
|
||||
/// This method runs in a potentially parallel fashion.
|
||||
pub fn par_eq(&self, other: &Self) -> bool {
|
||||
self.len() == other.len() && self.par_is_subset(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, A> HashSet<T, S, A>
|
||||
where
|
||||
T: Eq + Hash + Send,
|
||||
A: Allocator + Clone + Send,
|
||||
{
|
||||
/// Consumes (potentially in parallel) all values in an arbitrary order,
|
||||
/// while preserving the set's allocated memory for reuse.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn par_drain(&mut self) -> ParDrain<'_, T, A> {
|
||||
ParDrain {
|
||||
inner: self.map.par_drain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, S, A: Allocator + Clone + Send> IntoParallelIterator for HashSet<T, S, A> {
|
||||
type Item = T;
|
||||
type Iter = IntoParIter<T, A>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
IntoParIter {
|
||||
inner: self.map.into_par_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sync, S, A: Allocator + Clone> IntoParallelIterator for &'a HashSet<T, S, A> {
|
||||
type Item = &'a T;
|
||||
type Iter = ParIter<'a, T>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
ParIter {
|
||||
inner: self.map.par_keys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect values from a parallel iterator into a hashset.
|
||||
impl<T, S> FromParallelIterator<T> for HashSet<T, S, Global>
|
||||
where
|
||||
T: Eq + Hash + Send,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
fn from_par_iter<P>(par_iter: P) -> Self
|
||||
where
|
||||
P: IntoParallelIterator<Item = T>,
|
||||
{
|
||||
let mut set = HashSet::default();
|
||||
set.par_extend(par_iter);
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a hash set with items from a parallel iterator.
|
||||
impl<T, S> ParallelExtend<T> for HashSet<T, S, Global>
|
||||
where
|
||||
T: Eq + Hash + Send,
|
||||
S: BuildHasher,
|
||||
{
|
||||
fn par_extend<I>(&mut self, par_iter: I)
|
||||
where
|
||||
I: IntoParallelIterator<Item = T>,
|
||||
{
|
||||
extend(self, par_iter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a hash set with copied items from a parallel iterator.
|
||||
impl<'a, T, S> ParallelExtend<&'a T> for HashSet<T, S, Global>
|
||||
where
|
||||
T: 'a + Copy + Eq + Hash + Sync,
|
||||
S: BuildHasher,
|
||||
{
|
||||
fn par_extend<I>(&mut self, par_iter: I)
|
||||
where
|
||||
I: IntoParallelIterator<Item = &'a T>,
|
||||
{
|
||||
extend(self, par_iter);
|
||||
}
|
||||
}
|
||||
|
||||
// This is equal to the normal `HashSet` -- no custom advantage.
|
||||
fn extend<T, S, I, A>(set: &mut HashSet<T, S, A>, par_iter: I)
|
||||
where
|
||||
T: Eq + Hash,
|
||||
S: BuildHasher,
|
||||
A: Allocator + Clone,
|
||||
I: IntoParallelIterator,
|
||||
HashSet<T, S, A>: Extend<I::Item>,
|
||||
{
|
||||
let (list, len) = super::helpers::collect(par_iter);
|
||||
|
||||
// Values may be already present or show multiple times in the iterator.
|
||||
// Reserve the entire length if the set is empty.
|
||||
// Otherwise reserve half the length (rounded up), so the set
|
||||
// will only resize twice in the worst case.
|
||||
let reserve = if set.is_empty() { len } else { (len + 1) / 2 };
|
||||
set.reserve(reserve);
|
||||
for vec in list {
|
||||
set.extend(vec);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_par_set {
|
||||
use alloc::vec::Vec;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::hash_set::HashSet;
|
||||
|
||||
#[test]
|
||||
fn test_disjoint() {
|
||||
let mut xs = HashSet::new();
|
||||
let mut ys = HashSet::new();
|
||||
assert!(xs.par_is_disjoint(&ys));
|
||||
assert!(ys.par_is_disjoint(&xs));
|
||||
assert!(xs.insert(5));
|
||||
assert!(ys.insert(11));
|
||||
assert!(xs.par_is_disjoint(&ys));
|
||||
assert!(ys.par_is_disjoint(&xs));
|
||||
assert!(xs.insert(7));
|
||||
assert!(xs.insert(19));
|
||||
assert!(xs.insert(4));
|
||||
assert!(ys.insert(2));
|
||||
assert!(ys.insert(-11));
|
||||
assert!(xs.par_is_disjoint(&ys));
|
||||
assert!(ys.par_is_disjoint(&xs));
|
||||
assert!(ys.insert(7));
|
||||
assert!(!xs.par_is_disjoint(&ys));
|
||||
assert!(!ys.par_is_disjoint(&xs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subset_and_superset() {
|
||||
let mut a = HashSet::new();
|
||||
assert!(a.insert(0));
|
||||
assert!(a.insert(5));
|
||||
assert!(a.insert(11));
|
||||
assert!(a.insert(7));
|
||||
|
||||
let mut b = HashSet::new();
|
||||
assert!(b.insert(0));
|
||||
assert!(b.insert(7));
|
||||
assert!(b.insert(19));
|
||||
assert!(b.insert(250));
|
||||
assert!(b.insert(11));
|
||||
assert!(b.insert(200));
|
||||
|
||||
assert!(!a.par_is_subset(&b));
|
||||
assert!(!a.par_is_superset(&b));
|
||||
assert!(!b.par_is_subset(&a));
|
||||
assert!(!b.par_is_superset(&a));
|
||||
|
||||
assert!(b.insert(5));
|
||||
|
||||
assert!(a.par_is_subset(&b));
|
||||
assert!(!a.par_is_superset(&b));
|
||||
assert!(!b.par_is_subset(&a));
|
||||
assert!(b.par_is_superset(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterate() {
|
||||
let mut a = HashSet::new();
|
||||
for i in 0..32 {
|
||||
assert!(a.insert(i));
|
||||
}
|
||||
let observed = AtomicUsize::new(0);
|
||||
a.par_iter().for_each(|k| {
|
||||
observed.fetch_or(1 << *k, Ordering::Relaxed);
|
||||
});
|
||||
assert_eq!(observed.into_inner(), 0xFFFF_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intersection() {
|
||||
let mut a = HashSet::new();
|
||||
let mut b = HashSet::new();
|
||||
|
||||
assert!(a.insert(11));
|
||||
assert!(a.insert(1));
|
||||
assert!(a.insert(3));
|
||||
assert!(a.insert(77));
|
||||
assert!(a.insert(103));
|
||||
assert!(a.insert(5));
|
||||
assert!(a.insert(-5));
|
||||
|
||||
assert!(b.insert(2));
|
||||
assert!(b.insert(11));
|
||||
assert!(b.insert(77));
|
||||
assert!(b.insert(-9));
|
||||
assert!(b.insert(-42));
|
||||
assert!(b.insert(5));
|
||||
assert!(b.insert(3));
|
||||
|
||||
let expected = [3, 5, 11, 77];
|
||||
let i = a
|
||||
.par_intersection(&b)
|
||||
.map(|x| {
|
||||
assert!(expected.contains(x));
|
||||
1
|
||||
})
|
||||
.sum::<usize>();
|
||||
assert_eq!(i, expected.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_difference() {
|
||||
let mut a = HashSet::new();
|
||||
let mut b = HashSet::new();
|
||||
|
||||
assert!(a.insert(1));
|
||||
assert!(a.insert(3));
|
||||
assert!(a.insert(5));
|
||||
assert!(a.insert(9));
|
||||
assert!(a.insert(11));
|
||||
|
||||
assert!(b.insert(3));
|
||||
assert!(b.insert(9));
|
||||
|
||||
let expected = [1, 5, 11];
|
||||
let i = a
|
||||
.par_difference(&b)
|
||||
.map(|x| {
|
||||
assert!(expected.contains(x));
|
||||
1
|
||||
})
|
||||
.sum::<usize>();
|
||||
assert_eq!(i, expected.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symmetric_difference() {
|
||||
let mut a = HashSet::new();
|
||||
let mut b = HashSet::new();
|
||||
|
||||
assert!(a.insert(1));
|
||||
assert!(a.insert(3));
|
||||
assert!(a.insert(5));
|
||||
assert!(a.insert(9));
|
||||
assert!(a.insert(11));
|
||||
|
||||
assert!(b.insert(-2));
|
||||
assert!(b.insert(3));
|
||||
assert!(b.insert(9));
|
||||
assert!(b.insert(14));
|
||||
assert!(b.insert(22));
|
||||
|
||||
let expected = [-2, 1, 5, 11, 14, 22];
|
||||
let i = a
|
||||
.par_symmetric_difference(&b)
|
||||
.map(|x| {
|
||||
assert!(expected.contains(x));
|
||||
1
|
||||
})
|
||||
.sum::<usize>();
|
||||
assert_eq!(i, expected.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_union() {
|
||||
let mut a = HashSet::new();
|
||||
let mut b = HashSet::new();
|
||||
|
||||
assert!(a.insert(1));
|
||||
assert!(a.insert(3));
|
||||
assert!(a.insert(5));
|
||||
assert!(a.insert(9));
|
||||
assert!(a.insert(11));
|
||||
assert!(a.insert(16));
|
||||
assert!(a.insert(19));
|
||||
assert!(a.insert(24));
|
||||
|
||||
assert!(b.insert(-2));
|
||||
assert!(b.insert(1));
|
||||
assert!(b.insert(5));
|
||||
assert!(b.insert(9));
|
||||
assert!(b.insert(13));
|
||||
assert!(b.insert(19));
|
||||
|
||||
let expected = [-2, 1, 3, 5, 9, 11, 13, 16, 19, 24];
|
||||
let i = a
|
||||
.par_union(&b)
|
||||
.map(|x| {
|
||||
assert!(expected.contains(x));
|
||||
1
|
||||
})
|
||||
.sum::<usize>();
|
||||
assert_eq!(i, expected.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_iter() {
|
||||
let xs = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
let set: HashSet<_> = xs.par_iter().cloned().collect();
|
||||
|
||||
for x in &xs {
|
||||
assert!(set.contains(x));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_iter() {
|
||||
let hs = {
|
||||
let mut hs = HashSet::new();
|
||||
|
||||
hs.insert('a');
|
||||
hs.insert('b');
|
||||
|
||||
hs
|
||||
};
|
||||
|
||||
let v = hs.into_par_iter().collect::<Vec<char>>();
|
||||
assert!(v == ['a', 'b'] || v == ['b', 'a']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq() {
|
||||
// These constants once happened to expose a bug in insert().
|
||||
// I'm keeping them around to prevent a regression.
|
||||
let mut s1 = HashSet::new();
|
||||
|
||||
s1.insert(1);
|
||||
s1.insert(2);
|
||||
s1.insert(3);
|
||||
|
||||
let mut s2 = HashSet::new();
|
||||
|
||||
s2.insert(1);
|
||||
s2.insert(2);
|
||||
|
||||
assert!(!s1.par_eq(&s2));
|
||||
|
||||
s2.insert(3);
|
||||
|
||||
assert!(s1.par_eq(&s2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_ref() {
|
||||
let mut a = HashSet::new();
|
||||
a.insert(1);
|
||||
|
||||
a.par_extend(&[2, 3, 4][..]);
|
||||
|
||||
assert_eq!(a.len(), 4);
|
||||
assert!(a.contains(&1));
|
||||
assert!(a.contains(&2));
|
||||
assert!(a.contains(&3));
|
||||
assert!(a.contains(&4));
|
||||
|
||||
let mut b = HashSet::new();
|
||||
b.insert(5);
|
||||
b.insert(6);
|
||||
|
||||
a.par_extend(&b);
|
||||
|
||||
assert_eq!(a.len(), 6);
|
||||
assert!(a.contains(&1));
|
||||
assert!(a.contains(&2));
|
||||
assert!(a.contains(&3));
|
||||
assert!(a.contains(&4));
|
||||
assert!(a.contains(&5));
|
||||
assert!(a.contains(&6));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
mod size_hint {
|
||||
use core::cmp;
|
||||
|
||||
/// This presumably exists to prevent denial of service attacks.
|
||||
///
|
||||
/// Original discussion: https://github.com/serde-rs/serde/issues/1114.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub(super) fn cautious(hint: Option<usize>) -> usize {
|
||||
cmp::min(hint.unwrap_or(0), 4096)
|
||||
}
|
||||
}
|
||||
|
||||
mod map {
|
||||
use core::fmt;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::marker::PhantomData;
|
||||
use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::hash_map::HashMap;
|
||||
|
||||
use super::size_hint;
|
||||
|
||||
impl<K, V, H> Serialize for HashMap<K, V, H>
|
||||
where
|
||||
K: Serialize + Eq + Hash,
|
||||
V: Serialize,
|
||||
H: BuildHasher,
|
||||
{
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_map(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, K, V, S> Deserialize<'de> for HashMap<K, V, S>
|
||||
where
|
||||
K: Deserialize<'de> + Eq + Hash,
|
||||
V: Deserialize<'de>,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct MapVisitor<K, V, S> {
|
||||
marker: PhantomData<HashMap<K, V, S>>,
|
||||
}
|
||||
|
||||
impl<'de, K, V, S> Visitor<'de> for MapVisitor<K, V, S>
|
||||
where
|
||||
K: Deserialize<'de> + Eq + Hash,
|
||||
V: Deserialize<'de>,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
type Value = HashMap<K, V, S>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map")
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut values = HashMap::with_capacity_and_hasher(
|
||||
size_hint::cautious(map.size_hint()),
|
||||
S::default(),
|
||||
);
|
||||
|
||||
while let Some((key, value)) = map.next_entry()? {
|
||||
values.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
}
|
||||
|
||||
let visitor = MapVisitor {
|
||||
marker: PhantomData,
|
||||
};
|
||||
deserializer.deserialize_map(visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod set {
|
||||
use core::fmt;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::marker::PhantomData;
|
||||
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::hash_set::HashSet;
|
||||
|
||||
use super::size_hint;
|
||||
|
||||
impl<T, H> Serialize for HashSet<T, H>
|
||||
where
|
||||
T: Serialize + Eq + Hash,
|
||||
H: BuildHasher,
|
||||
{
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_seq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T, S> Deserialize<'de> for HashSet<T, S>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct SeqVisitor<T, S> {
|
||||
marker: PhantomData<HashSet<T, S>>,
|
||||
}
|
||||
|
||||
impl<'de, T, S> Visitor<'de> for SeqVisitor<T, S>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
type Value = HashSet<T, S>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a sequence")
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut values = HashSet::with_capacity_and_hasher(
|
||||
size_hint::cautious(seq.size_hint()),
|
||||
S::default(),
|
||||
);
|
||||
|
||||
while let Some(value) = seq.next_element()? {
|
||||
values.insert(value);
|
||||
}
|
||||
|
||||
Ok(values)
|
||||
}
|
||||
}
|
||||
|
||||
let visitor = SeqVisitor {
|
||||
marker: PhantomData,
|
||||
};
|
||||
deserializer.deserialize_seq(visitor)
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct SeqInPlaceVisitor<'a, T, S>(&'a mut HashSet<T, S>);
|
||||
|
||||
impl<'a, 'de, T, S> Visitor<'de> for SeqInPlaceVisitor<'a, T, S>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
{
|
||||
type Value = ();
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a sequence")
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
self.0.clear();
|
||||
self.0.reserve(size_hint::cautious(seq.size_hint()));
|
||||
|
||||
while let Some(value) = seq.next_element()? {
|
||||
self.0.insert(value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(SeqInPlaceVisitor(place))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
//! This crate is a Rust port of Google's high-performance [SwissTable] hash
|
||||
//! map, adapted to make it a drop-in replacement for Rust's standard `HashMap`
|
||||
//! and `HashSet` types.
|
||||
//!
|
||||
//! The original C++ version of [SwissTable] can be found [here], and this
|
||||
//! [CppCon talk] gives an overview of how the algorithm works.
|
||||
//!
|
||||
//! [SwissTable]: https://abseil.io/blog/20180927-swisstables
|
||||
//! [here]: https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h
|
||||
//! [CppCon talk]: https://www.youtube.com/watch?v=ncHmEUmJZf4
|
||||
|
||||
#![no_std]
|
||||
#![cfg_attr(
|
||||
feature = "nightly",
|
||||
feature(
|
||||
test,
|
||||
core_intrinsics,
|
||||
dropck_eyepatch,
|
||||
min_specialization,
|
||||
extend_one,
|
||||
allocator_api,
|
||||
slice_ptr_get,
|
||||
nonnull_slice_from_raw_parts,
|
||||
maybe_uninit_array_assume_init,
|
||||
build_hasher_simple_hash_one
|
||||
)
|
||||
)]
|
||||
#![allow(
|
||||
clippy::doc_markdown,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::option_if_let_else,
|
||||
clippy::redundant_else,
|
||||
clippy::manual_map,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::missing_errors_doc
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[cfg_attr(test, macro_use)]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(doctest)]
|
||||
doc_comment::doctest!("../README.md");
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[cfg(feature = "raw")]
|
||||
/// Experimental and unsafe `RawTable` API. This module is only available if the
|
||||
/// `raw` feature is enabled.
|
||||
pub mod raw {
|
||||
// The RawTable API is still experimental and is not properly documented yet.
|
||||
#[allow(missing_docs)]
|
||||
#[path = "mod.rs"]
|
||||
mod inner;
|
||||
pub use inner::*;
|
||||
|
||||
#[cfg(feature = "rayon")]
|
||||
/// [rayon]-based parallel iterator types for hash maps.
|
||||
/// 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"))]
|
||||
mod raw;
|
||||
|
||||
mod external_trait_impls;
|
||||
mod map;
|
||||
#[cfg(feature = "rustc-internal-api")]
|
||||
mod rustc_entry;
|
||||
mod scopeguard;
|
||||
mod set;
|
||||
|
||||
pub mod hash_map {
|
||||
//! A hash map implemented with quadratic probing and SIMD lookup.
|
||||
pub use crate::map::*;
|
||||
|
||||
#[cfg(feature = "rustc-internal-api")]
|
||||
pub use crate::rustc_entry::*;
|
||||
|
||||
#[cfg(feature = "rayon")]
|
||||
/// [rayon]-based parallel iterator types for hash maps.
|
||||
/// 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::map::*;
|
||||
}
|
||||
}
|
||||
pub mod hash_set {
|
||||
//! A hash set implemented as a `HashMap` where the value is `()`.
|
||||
pub use crate::set::*;
|
||||
|
||||
#[cfg(feature = "rayon")]
|
||||
/// [rayon]-based parallel iterator types for hash sets.
|
||||
/// 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::set::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::map::HashMap;
|
||||
pub use crate::set::HashSet;
|
||||
|
||||
/// The error type for `try_reserve` methods.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum TryReserveError {
|
||||
/// Error due to the computed capacity exceeding the collection's maximum
|
||||
/// (usually `isize::MAX` bytes).
|
||||
CapacityOverflow,
|
||||
|
||||
/// The memory allocator returned an error
|
||||
AllocError {
|
||||
/// The layout of the allocation request that failed.
|
||||
layout: alloc::alloc::Layout,
|
||||
},
|
||||
}
|
||||
|
||||
/// Wrapper around `Bump` which allows it to be used as an allocator for
|
||||
/// `HashMap`, `HashSet` and `RawTable`.
|
||||
///
|
||||
/// `Bump` can be used directly without this wrapper on nightly if you enable
|
||||
/// the `allocator-api` feature of the `bumpalo` crate.
|
||||
#[cfg(feature = "bumpalo")]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BumpWrapper<'a>(pub &'a bumpalo::Bump);
|
||||
|
||||
#[cfg(feature = "bumpalo")]
|
||||
#[test]
|
||||
fn test_bumpalo() {
|
||||
use bumpalo::Bump;
|
||||
let bump = Bump::new();
|
||||
let mut map = HashMap::new_in(BumpWrapper(&bump));
|
||||
map.insert(0, 1);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// See the cfg-if crate.
|
||||
#[allow(unused_macro_rules)]
|
||||
macro_rules! cfg_if {
|
||||
// match if/else chains with a final `else`
|
||||
($(
|
||||
if #[cfg($($meta:meta),*)] { $($it:item)* }
|
||||
) else * else {
|
||||
$($it2:item)*
|
||||
}) => {
|
||||
cfg_if! {
|
||||
@__items
|
||||
() ;
|
||||
$( ( ($($meta),*) ($($it)*) ), )*
|
||||
( () ($($it2)*) ),
|
||||
}
|
||||
};
|
||||
|
||||
// match if/else chains lacking a final `else`
|
||||
(
|
||||
if #[cfg($($i_met:meta),*)] { $($i_it:item)* }
|
||||
$(
|
||||
else if #[cfg($($e_met:meta),*)] { $($e_it:item)* }
|
||||
)*
|
||||
) => {
|
||||
cfg_if! {
|
||||
@__items
|
||||
() ;
|
||||
( ($($i_met),*) ($($i_it)*) ),
|
||||
$( ( ($($e_met),*) ($($e_it)*) ), )*
|
||||
( () () ),
|
||||
}
|
||||
};
|
||||
|
||||
// Internal and recursive macro to emit all the items
|
||||
//
|
||||
// Collects all the negated cfgs in a list at the beginning and after the
|
||||
// semicolon is all the remaining items
|
||||
(@__items ($($not:meta,)*) ; ) => {};
|
||||
(@__items ($($not:meta,)*) ; ( ($($m:meta),*) ($($it:item)*) ), $($rest:tt)*) => {
|
||||
// Emit all items within one block, applying an approprate #[cfg]. The
|
||||
// #[cfg] will require all `$m` matchers specified and must also negate
|
||||
// all previous matchers.
|
||||
cfg_if! { @__apply cfg(all($($m,)* not(any($($not),*)))), $($it)* }
|
||||
|
||||
// Recurse to emit all other items in `$rest`, and when we do so add all
|
||||
// our `$m` matchers to the list of `$not` matchers as future emissions
|
||||
// will have to negate everything we just matched as well.
|
||||
cfg_if! { @__items ($($not,)* $($m,)*) ; $($rest)* }
|
||||
};
|
||||
|
||||
// Internal macro to Apply a cfg attribute to a list of items
|
||||
(@__apply $m:meta, $($it:item)*) => {
|
||||
$(#[$m] $it)*
|
||||
};
|
||||
}
|
||||
|
||||
// Helper macro for specialization. This also helps avoid parse errors if the
|
||||
// default fn syntax for specialization changes in the future.
|
||||
#[cfg(feature = "nightly")]
|
||||
macro_rules! default_fn {
|
||||
(#[$($a:tt)*] $($tt:tt)*) => {
|
||||
#[$($a)*] default $($tt)*
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
macro_rules! default_fn {
|
||||
($($tt:tt)*) => {
|
||||
$($tt)*
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,73 @@
|
|||
pub(crate) use self::inner::{do_alloc, Allocator, Global};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
mod inner {
|
||||
use crate::alloc::alloc::Layout;
|
||||
pub use crate::alloc::alloc::{Allocator, Global};
|
||||
use core::ptr::NonNull;
|
||||
|
||||
#[allow(clippy::map_err_ignore)]
|
||||
pub fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<u8>, ()> {
|
||||
match alloc.allocate(layout) {
|
||||
Ok(ptr) => Ok(ptr.as_non_null_ptr()),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bumpalo")]
|
||||
unsafe impl Allocator for crate::BumpWrapper<'_> {
|
||||
#[inline]
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, core::alloc::AllocError> {
|
||||
match self.0.try_alloc_layout(layout) {
|
||||
Ok(ptr) => Ok(NonNull::slice_from_raw_parts(ptr, layout.size())),
|
||||
Err(_) => Err(core::alloc::AllocError),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
mod inner {
|
||||
use crate::alloc::alloc::{alloc, dealloc, Layout};
|
||||
use core::ptr::NonNull;
|
||||
|
||||
#[allow(clippy::missing_safety_doc)] // not exposed outside of this crate
|
||||
pub unsafe trait Allocator {
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<u8>, ()>;
|
||||
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Global;
|
||||
unsafe impl Allocator for Global {
|
||||
#[inline]
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<u8>, ()> {
|
||||
unsafe { NonNull::new(alloc(layout)).ok_or(()) }
|
||||
}
|
||||
#[inline]
|
||||
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
|
||||
dealloc(ptr.as_ptr(), layout);
|
||||
}
|
||||
}
|
||||
impl Default for Global {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Global
|
||||
}
|
||||
}
|
||||
|
||||
pub fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<u8>, ()> {
|
||||
alloc.allocate(layout)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bumpalo")]
|
||||
unsafe impl Allocator for crate::BumpWrapper<'_> {
|
||||
#[allow(clippy::map_err_ignore)]
|
||||
fn allocate(&self, layout: Layout) -> Result<NonNull<u8>, ()> {
|
||||
self.0.try_alloc_layout(layout).map_err(|_| ())
|
||||
}
|
||||
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
use super::imp::{BitMaskWord, BITMASK_MASK, BITMASK_STRIDE};
|
||||
#[cfg(feature = "nightly")]
|
||||
use core::intrinsics;
|
||||
|
||||
/// A bit mask which contains the result of a `Match` operation on a `Group` and
|
||||
/// allows iterating through them.
|
||||
///
|
||||
/// The bit mask is arranged so that low-order bits represent lower memory
|
||||
/// addresses for group match results.
|
||||
///
|
||||
/// For implementation reasons, the bits in the set may be sparsely packed, so
|
||||
/// that there is only one bit-per-byte used (the high bit, 7). If this is the
|
||||
/// case, `BITMASK_STRIDE` will be 8 to indicate a divide-by-8 should be
|
||||
/// performed on counts/indices to normalize this difference. `BITMASK_MASK` is
|
||||
/// similarly a mask of all the actually-used bits.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BitMask(pub BitMaskWord);
|
||||
|
||||
#[allow(clippy::use_self)]
|
||||
impl BitMask {
|
||||
/// Returns a new `BitMask` with all bits inverted.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn invert(self) -> Self {
|
||||
BitMask(self.0 ^ BITMASK_MASK)
|
||||
}
|
||||
|
||||
/// Flip the bit in the mask for the entry at the given index.
|
||||
///
|
||||
/// Returns the bit's previous state.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
#[cfg(feature = "raw")]
|
||||
pub unsafe fn flip(&mut self, index: usize) -> bool {
|
||||
// NOTE: The + BITMASK_STRIDE - 1 is to set the high bit.
|
||||
let mask = 1 << (index * BITMASK_STRIDE + BITMASK_STRIDE - 1);
|
||||
self.0 ^= mask;
|
||||
// The bit was set if the bit is now 0.
|
||||
self.0 & mask == 0
|
||||
}
|
||||
|
||||
/// Returns a new `BitMask` with the lowest bit removed.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn remove_lowest_bit(self) -> Self {
|
||||
BitMask(self.0 & (self.0 - 1))
|
||||
}
|
||||
/// Returns whether the `BitMask` has at least one set bit.
|
||||
#[inline]
|
||||
pub fn any_bit_set(self) -> bool {
|
||||
self.0 != 0
|
||||
}
|
||||
|
||||
/// Returns the first set bit in the `BitMask`, if there is one.
|
||||
#[inline]
|
||||
pub fn lowest_set_bit(self) -> Option<usize> {
|
||||
if self.0 == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { self.lowest_set_bit_nonzero() })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first set bit in the `BitMask`, if there is one. The
|
||||
/// bitmask must not be empty.
|
||||
#[inline]
|
||||
#[cfg(feature = "nightly")]
|
||||
pub unsafe fn lowest_set_bit_nonzero(self) -> usize {
|
||||
intrinsics::cttz_nonzero(self.0) as usize / BITMASK_STRIDE
|
||||
}
|
||||
#[inline]
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
pub unsafe fn lowest_set_bit_nonzero(self) -> usize {
|
||||
self.trailing_zeros()
|
||||
}
|
||||
|
||||
/// Returns the number of trailing zeroes in the `BitMask`.
|
||||
#[inline]
|
||||
pub fn trailing_zeros(self) -> usize {
|
||||
// ARM doesn't have a trailing_zeroes instruction, and instead uses
|
||||
// reverse_bits (RBIT) + leading_zeroes (CLZ). However older ARM
|
||||
// versions (pre-ARMv7) don't have RBIT and need to emulate it
|
||||
// instead. Since we only have 1 bit set in each byte on ARM, we can
|
||||
// use swap_bytes (REV) + leading_zeroes instead.
|
||||
if cfg!(target_arch = "arm") && BITMASK_STRIDE % 8 == 0 {
|
||||
self.0.swap_bytes().leading_zeros() as usize / BITMASK_STRIDE
|
||||
} else {
|
||||
self.0.trailing_zeros() as usize / BITMASK_STRIDE
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of leading zeroes in the `BitMask`.
|
||||
#[inline]
|
||||
pub fn leading_zeros(self) -> usize {
|
||||
self.0.leading_zeros() as usize / BITMASK_STRIDE
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for BitMask {
|
||||
type Item = usize;
|
||||
type IntoIter = BitMaskIter;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> BitMaskIter {
|
||||
BitMaskIter(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the contents of a `BitMask`, returning the indices of set
|
||||
/// bits.
|
||||
pub struct BitMaskIter(BitMask);
|
||||
|
||||
impl Iterator for BitMaskIter {
|
||||
type Item = usize;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
let bit = self.0.lowest_set_bit()?;
|
||||
self.0 = self.0.remove_lowest_bit();
|
||||
Some(bit)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
use super::bitmask::BitMask;
|
||||
use super::EMPTY;
|
||||
use core::{mem, ptr};
|
||||
|
||||
// Use the native word size as the group size. Using a 64-bit group size on
|
||||
// a 32-bit architecture will just end up being more expensive because
|
||||
// shifts and multiplies will need to be emulated.
|
||||
#[cfg(any(
|
||||
target_pointer_width = "64",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86_64",
|
||||
target_arch = "wasm32",
|
||||
))]
|
||||
type GroupWord = u64;
|
||||
#[cfg(all(
|
||||
target_pointer_width = "32",
|
||||
not(target_arch = "aarch64"),
|
||||
not(target_arch = "x86_64"),
|
||||
not(target_arch = "wasm32"),
|
||||
))]
|
||||
type GroupWord = u32;
|
||||
|
||||
pub type BitMaskWord = GroupWord;
|
||||
pub const BITMASK_STRIDE: usize = 8;
|
||||
// We only care about the highest bit of each byte for the mask.
|
||||
#[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
|
||||
pub const BITMASK_MASK: BitMaskWord = 0x8080_8080_8080_8080_u64 as GroupWord;
|
||||
|
||||
/// Helper function to replicate a byte across a `GroupWord`.
|
||||
#[inline]
|
||||
fn repeat(byte: u8) -> GroupWord {
|
||||
GroupWord::from_ne_bytes([byte; Group::WIDTH])
|
||||
}
|
||||
|
||||
/// Abstraction over a group of control bytes which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a word-sized integer.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Group(GroupWord);
|
||||
|
||||
// We perform all operations in the native endianness, and convert to
|
||||
// little-endian just before creating a BitMask. The can potentially
|
||||
// enable the compiler to eliminate unnecessary byte swaps if we are
|
||||
// only checking whether a BitMask is empty.
|
||||
#[allow(clippy::use_self)]
|
||||
impl Group {
|
||||
/// Number of bytes in the group.
|
||||
pub const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty bytes, suitable for use as the initial
|
||||
/// value for an empty hash table.
|
||||
///
|
||||
/// This is guaranteed to be aligned to the group size.
|
||||
#[inline]
|
||||
pub const fn static_empty() -> &'static [u8; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedBytes {
|
||||
_align: [Group; 0],
|
||||
bytes: [u8; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_BYTES: AlignedBytes = AlignedBytes {
|
||||
_align: [],
|
||||
bytes: [EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_BYTES.bytes
|
||||
}
|
||||
|
||||
/// Loads a group of bytes starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub unsafe fn load(ptr: *const u8) -> Self {
|
||||
Group(ptr::read_unaligned(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of bytes starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn load_aligned(ptr: *const u8) -> Self {
|
||||
// FIXME: use align_offset once it stabilizes
|
||||
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
|
||||
Group(ptr::read(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of bytes to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn store_aligned(self, ptr: *mut u8) {
|
||||
// FIXME: use align_offset once it stabilizes
|
||||
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
|
||||
ptr::write(ptr.cast(), self.0);
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which *may*
|
||||
/// have the given value.
|
||||
///
|
||||
/// This function may return a false positive in certain cases where
|
||||
/// the byte in the group differs from the searched value only in its
|
||||
/// lowest bit. This is fine because:
|
||||
/// - This never happens for `EMPTY` and `DELETED`, only full entries.
|
||||
/// - The check for key equality will catch these.
|
||||
/// - This only happens if there is at least 1 true match.
|
||||
/// - The chance of this happening is very low (< 1% chance per byte).
|
||||
#[inline]
|
||||
pub fn match_byte(self, byte: u8) -> BitMask {
|
||||
// This algorithm is derived from
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
|
||||
let cmp = self.0 ^ repeat(byte);
|
||||
BitMask((cmp.wrapping_sub(repeat(0x01)) & !cmp & repeat(0x80)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub fn match_empty(self) -> BitMask {
|
||||
// If the high bit is set, then the byte must be either:
|
||||
// 1111_1111 (EMPTY) or 1000_0000 (DELETED).
|
||||
// So we can just check if the top two bits are 1 by ANDing them.
|
||||
BitMask((self.0 & (self.0 << 1) & repeat(0x80)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub fn match_empty_or_deleted(self) -> BitMask {
|
||||
// A byte is EMPTY or DELETED iff the high bit is set
|
||||
BitMask((self.0 & repeat(0x80)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are full.
|
||||
#[inline]
|
||||
pub fn match_full(self) -> BitMask {
|
||||
self.match_empty_or_deleted().invert()
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all bytes in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub fn convert_special_to_empty_and_full_to_deleted(self) -> Self {
|
||||
// Map high_bit = 1 (EMPTY or DELETED) to 1111_1111
|
||||
// and high_bit = 0 (FULL) to 1000_0000
|
||||
//
|
||||
// Here's this logic expanded to concrete values:
|
||||
// let full = 1000_0000 (true) or 0000_0000 (false)
|
||||
// !1000_0000 + 1 = 0111_1111 + 1 = 1000_0000 (no carry)
|
||||
// !0000_0000 + 0 = 1111_1111 + 0 = 1111_1111 (no carry)
|
||||
let full = !self.0 & repeat(0x80);
|
||||
Group(!full + (full >> 7))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,146 @@
|
|||
use super::bitmask::BitMask;
|
||||
use super::EMPTY;
|
||||
use core::mem;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
use core::arch::x86;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use core::arch::x86_64 as x86;
|
||||
|
||||
pub type BitMaskWord = u16;
|
||||
pub const BITMASK_STRIDE: usize = 1;
|
||||
pub const BITMASK_MASK: BitMaskWord = 0xffff;
|
||||
|
||||
/// Abstraction over a group of control bytes which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a 128-bit SSE value.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Group(x86::__m128i);
|
||||
|
||||
// FIXME: https://github.com/rust-lang/rust-clippy/issues/3859
|
||||
#[allow(clippy::use_self)]
|
||||
impl Group {
|
||||
/// Number of bytes in the group.
|
||||
pub const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty bytes, suitable for use as the initial
|
||||
/// value for an empty hash table.
|
||||
///
|
||||
/// This is guaranteed to be aligned to the group size.
|
||||
#[inline]
|
||||
#[allow(clippy::items_after_statements)]
|
||||
pub const fn static_empty() -> &'static [u8; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedBytes {
|
||||
_align: [Group; 0],
|
||||
bytes: [u8; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_BYTES: AlignedBytes = AlignedBytes {
|
||||
_align: [],
|
||||
bytes: [EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_BYTES.bytes
|
||||
}
|
||||
|
||||
/// Loads a group of bytes starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub unsafe fn load(ptr: *const u8) -> Self {
|
||||
Group(x86::_mm_loadu_si128(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of bytes starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn load_aligned(ptr: *const u8) -> Self {
|
||||
// FIXME: use align_offset once it stabilizes
|
||||
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
|
||||
Group(x86::_mm_load_si128(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of bytes to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub unsafe fn store_aligned(self, ptr: *mut u8) {
|
||||
// FIXME: use align_offset once it stabilizes
|
||||
debug_assert_eq!(ptr as usize & (mem::align_of::<Self>() - 1), 0);
|
||||
x86::_mm_store_si128(ptr.cast(), self.0);
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which have
|
||||
/// the given value.
|
||||
#[inline]
|
||||
pub fn match_byte(self, byte: u8) -> BitMask {
|
||||
#[allow(
|
||||
clippy::cast_possible_wrap, // byte: u8 as i8
|
||||
// byte: i32 as u16
|
||||
// note: _mm_movemask_epi8 returns a 16-bit mask in a i32, the
|
||||
// upper 16-bits of the i32 are zeroed:
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_truncation
|
||||
)]
|
||||
unsafe {
|
||||
let cmp = x86::_mm_cmpeq_epi8(self.0, x86::_mm_set1_epi8(byte as i8));
|
||||
BitMask(x86::_mm_movemask_epi8(cmp) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub fn match_empty(self) -> BitMask {
|
||||
self.match_byte(EMPTY)
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub fn match_empty_or_deleted(self) -> BitMask {
|
||||
#[allow(
|
||||
// byte: i32 as u16
|
||||
// note: _mm_movemask_epi8 returns a 16-bit mask in a i32, the
|
||||
// upper 16-bits of the i32 are zeroed:
|
||||
clippy::cast_sign_loss,
|
||||
clippy::cast_possible_truncation
|
||||
)]
|
||||
unsafe {
|
||||
// A byte is EMPTY or DELETED iff the high bit is set
|
||||
BitMask(x86::_mm_movemask_epi8(self.0) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all bytes in the group which are full.
|
||||
#[inline]
|
||||
pub fn match_full(&self) -> BitMask {
|
||||
self.match_empty_or_deleted().invert()
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all bytes in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub fn convert_special_to_empty_and_full_to_deleted(self) -> Self {
|
||||
// Map high_bit = 1 (EMPTY or DELETED) to 1111_1111
|
||||
// and high_bit = 0 (FULL) to 1000_0000
|
||||
//
|
||||
// Here's this logic expanded to concrete values:
|
||||
// let special = 0 > byte = 1111_1111 (true) or 0000_0000 (false)
|
||||
// 1111_1111 | 1000_0000 = 1111_1111
|
||||
// 0000_0000 | 1000_0000 = 1000_0000
|
||||
#[allow(
|
||||
clippy::cast_possible_wrap, // byte: 0x80_u8 as i8
|
||||
)]
|
||||
unsafe {
|
||||
let zero = x86::_mm_setzero_si128();
|
||||
let special = x86::_mm_cmpgt_epi8(zero, self.0);
|
||||
Group(x86::_mm_or_si128(
|
||||
special,
|
||||
x86::_mm_set1_epi8(0x80_u8 as i8),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,630 @@
|
|||
use self::RustcEntry::*;
|
||||
use crate::map::{make_insert_hash, Drain, HashMap, IntoIter, Iter, IterMut};
|
||||
use crate::raw::{Allocator, Bucket, Global, RawTable};
|
||||
use core::fmt::{self, Debug};
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::mem;
|
||||
|
||||
impl<K, V, S, A> HashMap<K, V, S, A>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
S: BuildHasher,
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
/// Gets the given key's corresponding entry in the map for in-place manipulation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut letters = HashMap::new();
|
||||
///
|
||||
/// for ch in "a short treatise on fungi".chars() {
|
||||
/// let counter = letters.rustc_entry(ch).or_insert(0);
|
||||
/// *counter += 1;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(letters[&'s'], 2);
|
||||
/// assert_eq!(letters[&'t'], 3);
|
||||
/// assert_eq!(letters[&'u'], 1);
|
||||
/// assert_eq!(letters.get(&'y'), None);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn rustc_entry(&mut self, key: K) -> RustcEntry<'_, K, V, A> {
|
||||
let hash = make_insert_hash(&self.hash_builder, &key);
|
||||
if let Some(elem) = self.table.find(hash, |q| q.0.eq(&key)) {
|
||||
RustcEntry::Occupied(RustcOccupiedEntry {
|
||||
key: Some(key),
|
||||
elem,
|
||||
table: &mut self.table,
|
||||
})
|
||||
} else {
|
||||
// Ideally we would put this in VacantEntry::insert, but Entry is not
|
||||
// generic over the BuildHasher and adding a generic parameter would be
|
||||
// a breaking change.
|
||||
self.reserve(1);
|
||||
|
||||
RustcEntry::Vacant(RustcVacantEntry {
|
||||
hash,
|
||||
key,
|
||||
table: &mut self.table,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into a single entry in a map, which may either be vacant or occupied.
|
||||
///
|
||||
/// This `enum` is constructed from the [`rustc_entry`] method on [`HashMap`].
|
||||
///
|
||||
/// [`HashMap`]: struct.HashMap.html
|
||||
/// [`rustc_entry`]: struct.HashMap.html#method.rustc_entry
|
||||
pub enum RustcEntry<'a, K, V, A = Global>
|
||||
where
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
/// An occupied entry.
|
||||
Occupied(RustcOccupiedEntry<'a, K, V, A>),
|
||||
|
||||
/// A vacant entry.
|
||||
Vacant(RustcVacantEntry<'a, K, V, A>),
|
||||
}
|
||||
|
||||
impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for RustcEntry<'_, K, V, A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(),
|
||||
Occupied(ref o) => f.debug_tuple("Entry").field(o).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into an occupied entry in a `HashMap`.
|
||||
/// It is part of the [`RustcEntry`] enum.
|
||||
///
|
||||
/// [`RustcEntry`]: enum.RustcEntry.html
|
||||
pub struct RustcOccupiedEntry<'a, K, V, A = Global>
|
||||
where
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
key: Option<K>,
|
||||
elem: Bucket<(K, V)>,
|
||||
table: &'a mut RawTable<(K, V), A>,
|
||||
}
|
||||
|
||||
unsafe impl<K, V, A> Send for RustcOccupiedEntry<'_, K, V, A>
|
||||
where
|
||||
K: Send,
|
||||
V: Send,
|
||||
A: Allocator + Clone + Send,
|
||||
{
|
||||
}
|
||||
unsafe impl<K, V, A> Sync for RustcOccupiedEntry<'_, K, V, A>
|
||||
where
|
||||
K: Sync,
|
||||
V: Sync,
|
||||
A: Allocator + Clone + Sync,
|
||||
{
|
||||
}
|
||||
|
||||
impl<K: Debug, V: Debug, A: Allocator + Clone> Debug for RustcOccupiedEntry<'_, K, V, A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OccupiedEntry")
|
||||
.field("key", self.key())
|
||||
.field("value", self.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into a vacant entry in a `HashMap`.
|
||||
/// It is part of the [`RustcEntry`] enum.
|
||||
///
|
||||
/// [`RustcEntry`]: enum.RustcEntry.html
|
||||
pub struct RustcVacantEntry<'a, K, V, A = Global>
|
||||
where
|
||||
A: Allocator + Clone,
|
||||
{
|
||||
hash: u64,
|
||||
key: K,
|
||||
table: &'a mut RawTable<(K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Debug, V, A: Allocator + Clone> Debug for RustcVacantEntry<'_, K, V, A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("VacantEntry").field(self.key()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V, A: Allocator + Clone> RustcEntry<'a, K, V, A> {
|
||||
/// Sets the value of the entry, and returns a RustcOccupiedEntry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// let entry = map.rustc_entry("horseyland").insert(37);
|
||||
///
|
||||
/// assert_eq!(entry.key(), &"horseyland");
|
||||
/// ```
|
||||
pub fn insert(self, value: V) -> RustcOccupiedEntry<'a, K, V, A> {
|
||||
match self {
|
||||
Vacant(entry) => entry.insert_entry(value),
|
||||
Occupied(mut entry) => {
|
||||
entry.insert(value);
|
||||
entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a value is in the entry by inserting the default if empty, and returns
|
||||
/// a mutable reference to the value in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
///
|
||||
/// map.rustc_entry("poneyland").or_insert(3);
|
||||
/// assert_eq!(map["poneyland"], 3);
|
||||
///
|
||||
/// *map.rustc_entry("poneyland").or_insert(10) *= 2;
|
||||
/// assert_eq!(map["poneyland"], 6);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn or_insert(self, default: V) -> &'a mut V
|
||||
where
|
||||
K: Hash,
|
||||
{
|
||||
match self {
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
Vacant(entry) => entry.insert(default),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures a value is in the entry by inserting the result of the default function if empty,
|
||||
/// and returns a mutable reference to the value in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, String> = HashMap::new();
|
||||
/// let s = "hoho".to_string();
|
||||
///
|
||||
/// map.rustc_entry("poneyland").or_insert_with(|| s);
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], "hoho".to_string());
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V
|
||||
where
|
||||
K: Hash,
|
||||
{
|
||||
match self {
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
Vacant(entry) => entry.insert(default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to this entry's key.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// assert_eq!(map.rustc_entry("poneyland").key(), &"poneyland");
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn key(&self) -> &K {
|
||||
match *self {
|
||||
Occupied(ref entry) => entry.key(),
|
||||
Vacant(ref entry) => entry.key(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides in-place mutable access to an occupied entry before any
|
||||
/// potential inserts into the map.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
///
|
||||
/// map.rustc_entry("poneyland")
|
||||
/// .and_modify(|e| { *e += 1 })
|
||||
/// .or_insert(42);
|
||||
/// assert_eq!(map["poneyland"], 42);
|
||||
///
|
||||
/// map.rustc_entry("poneyland")
|
||||
/// .and_modify(|e| { *e += 1 })
|
||||
/// .or_insert(42);
|
||||
/// assert_eq!(map["poneyland"], 43);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn and_modify<F>(self, f: F) -> Self
|
||||
where
|
||||
F: FnOnce(&mut V),
|
||||
{
|
||||
match self {
|
||||
Occupied(mut entry) => {
|
||||
f(entry.get_mut());
|
||||
Occupied(entry)
|
||||
}
|
||||
Vacant(entry) => Vacant(entry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V: Default, A: Allocator + Clone> RustcEntry<'a, K, V, A> {
|
||||
/// Ensures a value is in the entry by inserting the default value if empty,
|
||||
/// and returns a mutable reference to the value in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() {
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, Option<u32>> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_default();
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], None);
|
||||
/// # }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn or_default(self) -> &'a mut V
|
||||
where
|
||||
K: Hash,
|
||||
{
|
||||
match self {
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
Vacant(entry) => entry.insert(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V, A: Allocator + Clone> RustcOccupiedEntry<'a, K, V, A> {
|
||||
/// Gets a reference to the key in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
/// assert_eq!(map.rustc_entry("poneyland").key(), &"poneyland");
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn key(&self) -> &K {
|
||||
unsafe { &self.elem.as_ref().0 }
|
||||
}
|
||||
|
||||
/// Take the ownership of the key and value from the map.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// if let RustcEntry::Occupied(o) = map.rustc_entry("poneyland") {
|
||||
/// // We delete the entry from the map.
|
||||
/// o.remove_entry();
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map.contains_key("poneyland"), false);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn remove_entry(self) -> (K, V) {
|
||||
unsafe { self.table.remove(self.elem) }
|
||||
}
|
||||
|
||||
/// Gets a reference to the value in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// if let RustcEntry::Occupied(o) = map.rustc_entry("poneyland") {
|
||||
/// assert_eq!(o.get(), &12);
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn get(&self) -> &V {
|
||||
unsafe { &self.elem.as_ref().1 }
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the value in the entry.
|
||||
///
|
||||
/// If you need a reference to the `RustcOccupiedEntry` which may outlive the
|
||||
/// destruction of the `RustcEntry` value, see [`into_mut`].
|
||||
///
|
||||
/// [`into_mut`]: #method.into_mut
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], 12);
|
||||
/// if let RustcEntry::Occupied(mut o) = map.rustc_entry("poneyland") {
|
||||
/// *o.get_mut() += 10;
|
||||
/// assert_eq!(*o.get(), 22);
|
||||
///
|
||||
/// // We can use the same RustcEntry multiple times.
|
||||
/// *o.get_mut() += 2;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], 24);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn get_mut(&mut self) -> &mut V {
|
||||
unsafe { &mut self.elem.as_mut().1 }
|
||||
}
|
||||
|
||||
/// Converts the RustcOccupiedEntry into a mutable reference to the value in the entry
|
||||
/// with a lifetime bound to the map itself.
|
||||
///
|
||||
/// If you need multiple references to the `RustcOccupiedEntry`, see [`get_mut`].
|
||||
///
|
||||
/// [`get_mut`]: #method.get_mut
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], 12);
|
||||
/// if let RustcEntry::Occupied(o) = map.rustc_entry("poneyland") {
|
||||
/// *o.into_mut() += 10;
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], 22);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn into_mut(self) -> &'a mut V {
|
||||
unsafe { &mut self.elem.as_mut().1 }
|
||||
}
|
||||
|
||||
/// Sets the value of the entry, and returns the entry's old value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// if let RustcEntry::Occupied(mut o) = map.rustc_entry("poneyland") {
|
||||
/// assert_eq!(o.insert(15), 12);
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map["poneyland"], 15);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn insert(&mut self, value: V) -> V {
|
||||
mem::replace(self.get_mut(), value)
|
||||
}
|
||||
|
||||
/// Takes the value out of the entry, and returns it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// map.rustc_entry("poneyland").or_insert(12);
|
||||
///
|
||||
/// if let RustcEntry::Occupied(o) = map.rustc_entry("poneyland") {
|
||||
/// assert_eq!(o.remove(), 12);
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(map.contains_key("poneyland"), false);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn remove(self) -> V {
|
||||
self.remove_entry().1
|
||||
}
|
||||
|
||||
/// Replaces the entry, returning the old key and value. The new key in the hash map will be
|
||||
/// the key used to create this entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::hash_map::{RustcEntry, HashMap};
|
||||
/// use std::rc::Rc;
|
||||
///
|
||||
/// let mut map: HashMap<Rc<String>, u32> = HashMap::new();
|
||||
/// map.insert(Rc::new("Stringthing".to_string()), 15);
|
||||
///
|
||||
/// let my_key = Rc::new("Stringthing".to_string());
|
||||
///
|
||||
/// if let RustcEntry::Occupied(entry) = map.rustc_entry(my_key) {
|
||||
/// // Also replace the key with a handle to our other key.
|
||||
/// let (old_key, old_value): (Rc<String>, u32) = entry.replace_entry(16);
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn replace_entry(self, value: V) -> (K, V) {
|
||||
let entry = unsafe { self.elem.as_mut() };
|
||||
|
||||
let old_key = mem::replace(&mut entry.0, self.key.unwrap());
|
||||
let old_value = mem::replace(&mut entry.1, value);
|
||||
|
||||
(old_key, old_value)
|
||||
}
|
||||
|
||||
/// Replaces the key in the hash map with the key used to create this entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::hash_map::{RustcEntry, HashMap};
|
||||
/// use std::rc::Rc;
|
||||
///
|
||||
/// let mut map: HashMap<Rc<String>, u32> = HashMap::new();
|
||||
/// let mut known_strings: Vec<Rc<String>> = Vec::new();
|
||||
///
|
||||
/// // Initialise known strings, run program, etc.
|
||||
///
|
||||
/// reclaim_memory(&mut map, &known_strings);
|
||||
///
|
||||
/// fn reclaim_memory(map: &mut HashMap<Rc<String>, u32>, known_strings: &[Rc<String>] ) {
|
||||
/// for s in known_strings {
|
||||
/// if let RustcEntry::Occupied(entry) = map.rustc_entry(s.clone()) {
|
||||
/// // Replaces the entry's key with our version of it in `known_strings`.
|
||||
/// entry.replace_key();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn replace_key(self) -> K {
|
||||
let entry = unsafe { self.elem.as_mut() };
|
||||
mem::replace(&mut entry.0, self.key.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V, A: Allocator + Clone> RustcVacantEntry<'a, K, V, A> {
|
||||
/// Gets a reference to the key that would be used when inserting a value
|
||||
/// through the `RustcVacantEntry`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
/// assert_eq!(map.rustc_entry("poneyland").key(), &"poneyland");
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn key(&self) -> &K {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Take ownership of the key.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
///
|
||||
/// if let RustcEntry::Vacant(v) = map.rustc_entry("poneyland") {
|
||||
/// v.into_key();
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn into_key(self) -> K {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Sets the value of the entry with the RustcVacantEntry's key,
|
||||
/// and returns a mutable reference to it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
///
|
||||
/// if let RustcEntry::Vacant(o) = map.rustc_entry("poneyland") {
|
||||
/// o.insert(37);
|
||||
/// }
|
||||
/// assert_eq!(map["poneyland"], 37);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn insert(self, value: V) -> &'a mut V {
|
||||
unsafe {
|
||||
let bucket = self.table.insert_no_grow(self.hash, (self.key, value));
|
||||
&mut bucket.as_mut().1
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value of the entry with the RustcVacantEntry's key,
|
||||
/// and returns a RustcOccupiedEntry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hashbrown::HashMap;
|
||||
/// use hashbrown::hash_map::RustcEntry;
|
||||
///
|
||||
/// let mut map: HashMap<&str, u32> = HashMap::new();
|
||||
///
|
||||
/// if let RustcEntry::Vacant(v) = map.rustc_entry("poneyland") {
|
||||
/// let o = v.insert_entry(37);
|
||||
/// assert_eq!(o.get(), &37);
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn insert_entry(self, value: V) -> RustcOccupiedEntry<'a, K, V, A> {
|
||||
let bucket = unsafe { self.table.insert_no_grow(self.hash, (self.key, value)) };
|
||||
RustcOccupiedEntry {
|
||||
key: None,
|
||||
elem: bucket,
|
||||
table: self.table,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> IterMut<'_, K, V> {
|
||||
/// Returns a iterator of references over the remaining items.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn rustc_iter(&self) -> Iter<'_, K, V> {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> IntoIter<K, V> {
|
||||
/// Returns a iterator of references over the remaining items.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn rustc_iter(&self) -> Iter<'_, K, V> {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> Drain<'_, K, V> {
|
||||
/// Returns a iterator of references over the remaining items.
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
pub fn rustc_iter(&self) -> Iter<'_, K, V> {
|
||||
self.iter()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Extracted from the scopeguard crate
|
||||
use core::{
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr,
|
||||
};
|
||||
|
||||
pub struct ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
dropfn: F,
|
||||
value: T,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn guard<T, F>(value: T, dropfn: F) -> ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
ScopeGuard { dropfn, value }
|
||||
}
|
||||
|
||||
impl<T, F> ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
#[inline]
|
||||
pub fn into_inner(guard: Self) -> T {
|
||||
// Cannot move out of Drop-implementing types, so
|
||||
// ptr::read the value and forget the guard.
|
||||
unsafe {
|
||||
let value = ptr::read(&guard.value);
|
||||
// read the closure so that it is dropped, and assign it to a local
|
||||
// variable to ensure that it is only dropped after the guard has
|
||||
// been forgotten. (In case the Drop impl of the closure, or that
|
||||
// of any consumed captured variable, panics).
|
||||
let _dropfn = ptr::read(&guard.dropfn);
|
||||
mem::forget(guard);
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Deref for ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
type Target = T;
|
||||
#[inline]
|
||||
fn deref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> DerefMut for ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> Drop for ScopeGuard<T, F>
|
||||
where
|
||||
F: FnMut(&mut T),
|
||||
{
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
(self.dropfn)(&mut self.value);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,65 @@
|
|||
//! Sanity check that alternate hashers work correctly.
|
||||
|
||||
#![cfg(not(miri))] // FIXME: takes too long
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use std::hash::{BuildHasher, BuildHasherDefault, Hasher};
|
||||
|
||||
fn check<S: BuildHasher + Default>() {
|
||||
let range = 0..1_000;
|
||||
|
||||
let mut set = HashSet::<i32, S>::default();
|
||||
set.extend(range.clone());
|
||||
|
||||
assert!(!set.contains(&i32::min_value()));
|
||||
assert!(!set.contains(&(range.start - 1)));
|
||||
for i in range.clone() {
|
||||
assert!(set.contains(&i));
|
||||
}
|
||||
assert!(!set.contains(&range.end));
|
||||
assert!(!set.contains(&i32::max_value()));
|
||||
}
|
||||
|
||||
/// Use hashbrown's default hasher.
|
||||
#[test]
|
||||
fn default() {
|
||||
check::<hashbrown::hash_map::DefaultHashBuilder>();
|
||||
}
|
||||
|
||||
/// Use std's default hasher.
|
||||
#[test]
|
||||
fn random_state() {
|
||||
check::<std::collections::hash_map::RandomState>();
|
||||
}
|
||||
|
||||
/// Use a constant 0 hash.
|
||||
#[test]
|
||||
fn zero() {
|
||||
#[derive(Default)]
|
||||
struct ZeroHasher;
|
||||
|
||||
impl Hasher for ZeroHasher {
|
||||
fn finish(&self) -> u64 {
|
||||
0
|
||||
}
|
||||
fn write(&mut self, _: &[u8]) {}
|
||||
}
|
||||
|
||||
check::<BuildHasherDefault<ZeroHasher>>();
|
||||
}
|
||||
|
||||
/// Use a constant maximum hash.
|
||||
#[test]
|
||||
fn max() {
|
||||
#[derive(Default)]
|
||||
struct MaxHasher;
|
||||
|
||||
impl Hasher for MaxHasher {
|
||||
fn finish(&self) -> u64 {
|
||||
u64::max_value()
|
||||
}
|
||||
fn write(&mut self, _: &[u8]) {}
|
||||
}
|
||||
|
||||
check::<BuildHasherDefault<MaxHasher>>();
|
||||
}
|
|
@ -0,0 +1,533 @@
|
|||
#![cfg(feature = "rayon")]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rayon::iter::{
|
||||
IntoParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelExtend,
|
||||
ParallelIterator,
|
||||
};
|
||||
|
||||
macro_rules! assert_eq3 {
|
||||
($e1:expr, $e2:expr, $e3:expr) => {{
|
||||
assert_eq!($e1, $e2);
|
||||
assert_eq!($e1, $e3);
|
||||
assert_eq!($e2, $e3);
|
||||
}};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MAP_EMPTY: HashMap<char, u32> = HashMap::new();
|
||||
static ref MAP: HashMap<char, u32> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert('b', 20);
|
||||
m.insert('a', 10);
|
||||
m.insert('c', 30);
|
||||
m.insert('e', 50);
|
||||
m.insert('f', 60);
|
||||
m.insert('d', 40);
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_iter_empty() {
|
||||
let vec_seq = MAP_EMPTY.iter().collect::<Vec<_>>();
|
||||
let vec_par = MAP_EMPTY.par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_iter() {
|
||||
let mut vec_seq = MAP.iter().collect::<Vec<_>>();
|
||||
let mut vec_par = MAP.par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [
|
||||
(&'a', &10),
|
||||
(&'b', &20),
|
||||
(&'c', &30),
|
||||
(&'d', &40),
|
||||
(&'e', &50),
|
||||
(&'f', &60),
|
||||
];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_keys_empty() {
|
||||
let vec_seq = MAP_EMPTY.keys().collect::<Vec<&char>>();
|
||||
let vec_par = MAP_EMPTY.par_keys().collect::<Vec<&char>>();
|
||||
|
||||
let expected: [&char; 0] = [];
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_keys() {
|
||||
let mut vec_seq = MAP.keys().collect::<Vec<_>>();
|
||||
let mut vec_par = MAP.par_keys().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [&'a', &'b', &'c', &'d', &'e', &'f'];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_values_empty() {
|
||||
let vec_seq = MAP_EMPTY.values().collect::<Vec<_>>();
|
||||
let vec_par = MAP_EMPTY.par_values().collect::<Vec<_>>();
|
||||
|
||||
let expected: [&u32; 0] = [];
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_values() {
|
||||
let mut vec_seq = MAP.values().collect::<Vec<_>>();
|
||||
let mut vec_par = MAP.par_values().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [&10, &20, &30, &40, &50, &60];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_iter_mut_empty() {
|
||||
let mut map1 = MAP_EMPTY.clone();
|
||||
let mut map2 = MAP_EMPTY.clone();
|
||||
|
||||
let vec_seq = map1.iter_mut().collect::<Vec<_>>();
|
||||
let vec_par = map2.par_iter_mut().collect::<Vec<_>>();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_iter_mut() {
|
||||
let mut map1 = MAP.clone();
|
||||
let mut map2 = MAP.clone();
|
||||
|
||||
let mut vec_seq = map1.iter_mut().collect::<Vec<_>>();
|
||||
let mut vec_par = map2.par_iter_mut().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [
|
||||
(&'a', &mut 10),
|
||||
(&'b', &mut 20),
|
||||
(&'c', &mut 30),
|
||||
(&'d', &mut 40),
|
||||
(&'e', &mut 50),
|
||||
(&'f', &mut 60),
|
||||
];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_values_mut_empty() {
|
||||
let mut map1 = MAP_EMPTY.clone();
|
||||
let mut map2 = MAP_EMPTY.clone();
|
||||
|
||||
let vec_seq = map1.values_mut().collect::<Vec<_>>();
|
||||
let vec_par = map2.par_values_mut().collect::<Vec<_>>();
|
||||
|
||||
let expected: [&u32; 0] = [];
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_values_mut() {
|
||||
let mut map1 = MAP.clone();
|
||||
let mut map2 = MAP.clone();
|
||||
|
||||
let mut vec_seq = map1.values_mut().collect::<Vec<_>>();
|
||||
let mut vec_par = map2.par_values_mut().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [&mut 10, &mut 20, &mut 30, &mut 40, &mut 50, &mut 60];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_into_iter_empty() {
|
||||
let vec_seq = MAP_EMPTY.clone().into_iter().collect::<Vec<_>>();
|
||||
let vec_par = MAP_EMPTY.clone().into_par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_into_iter() {
|
||||
let mut vec_seq = MAP.clone().into_iter().collect::<Vec<_>>();
|
||||
let mut vec_par = MAP.clone().into_par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [
|
||||
('a', 10),
|
||||
('b', 20),
|
||||
('c', 30),
|
||||
('d', 40),
|
||||
('e', 50),
|
||||
('f', 60),
|
||||
];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MAP_VEC_EMPTY: Vec<(char, u32)> = vec![];
|
||||
static ref MAP_VEC: Vec<(char, u32)> = vec![
|
||||
('b', 20),
|
||||
('a', 10),
|
||||
('c', 30),
|
||||
('e', 50),
|
||||
('f', 60),
|
||||
('d', 40),
|
||||
];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_collect_empty() {
|
||||
let map_expected = MAP_EMPTY.clone();
|
||||
let map_seq = MAP_VEC_EMPTY.clone().into_iter().collect::<HashMap<_, _>>();
|
||||
let map_par = MAP_VEC_EMPTY
|
||||
.clone()
|
||||
.into_par_iter()
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
assert_eq!(map_seq, map_par);
|
||||
assert_eq!(map_seq, map_expected);
|
||||
assert_eq!(map_par, map_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_collect() {
|
||||
let map_expected = MAP.clone();
|
||||
let map_seq = MAP_VEC.clone().into_iter().collect::<HashMap<_, _>>();
|
||||
let map_par = MAP_VEC.clone().into_par_iter().collect::<HashMap<_, _>>();
|
||||
|
||||
assert_eq!(map_seq, map_par);
|
||||
assert_eq!(map_seq, map_expected);
|
||||
assert_eq!(map_par, map_expected);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MAP_EXISTING_EMPTY: HashMap<char, u32> = HashMap::new();
|
||||
static ref MAP_EXISTING: HashMap<char, u32> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert('b', 20);
|
||||
m.insert('a', 10);
|
||||
m
|
||||
};
|
||||
static ref MAP_EXTENSION_EMPTY: Vec<(char, u32)> = vec![];
|
||||
static ref MAP_EXTENSION: Vec<(char, u32)> = vec![('c', 30), ('e', 50), ('f', 60), ('d', 40),];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_existing_empty_extend_empty() {
|
||||
let expected = HashMap::new();
|
||||
let mut map_seq = MAP_EXISTING_EMPTY.clone();
|
||||
let mut map_par = MAP_EXISTING_EMPTY.clone();
|
||||
|
||||
map_seq.extend(MAP_EXTENSION_EMPTY.iter().copied());
|
||||
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().copied());
|
||||
|
||||
assert_eq3!(map_seq, map_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_existing_empty_extend() {
|
||||
let expected = MAP_EXTENSION.iter().copied().collect::<HashMap<_, _>>();
|
||||
let mut map_seq = MAP_EXISTING_EMPTY.clone();
|
||||
let mut map_par = MAP_EXISTING_EMPTY.clone();
|
||||
|
||||
map_seq.extend(MAP_EXTENSION.iter().copied());
|
||||
map_par.par_extend(MAP_EXTENSION.par_iter().copied());
|
||||
|
||||
assert_eq3!(map_seq, map_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_existing_extend_empty() {
|
||||
let expected = MAP_EXISTING.clone();
|
||||
let mut map_seq = MAP_EXISTING.clone();
|
||||
let mut map_par = MAP_EXISTING.clone();
|
||||
|
||||
map_seq.extend(MAP_EXTENSION_EMPTY.iter().copied());
|
||||
map_par.par_extend(MAP_EXTENSION_EMPTY.par_iter().copied());
|
||||
|
||||
assert_eq3!(map_seq, map_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_seq_par_equivalence_existing_extend() {
|
||||
let expected = MAP.clone();
|
||||
let mut map_seq = MAP_EXISTING.clone();
|
||||
let mut map_par = MAP_EXISTING.clone();
|
||||
|
||||
map_seq.extend(MAP_EXTENSION.iter().copied());
|
||||
map_par.par_extend(MAP_EXTENSION.par_iter().copied());
|
||||
|
||||
assert_eq3!(map_seq, map_par, expected);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SET_EMPTY: HashSet<char> = HashSet::new();
|
||||
static ref SET: HashSet<char> = {
|
||||
let mut s = HashSet::new();
|
||||
s.insert('b');
|
||||
s.insert('a');
|
||||
s.insert('c');
|
||||
s.insert('e');
|
||||
s.insert('f');
|
||||
s.insert('d');
|
||||
s
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_iter_empty() {
|
||||
let vec_seq = SET_EMPTY.iter().collect::<Vec<_>>();
|
||||
let vec_par = SET_EMPTY.par_iter().collect::<Vec<_>>();
|
||||
|
||||
let expected: [&char; 0] = [];
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_iter() {
|
||||
let mut vec_seq = SET.iter().collect::<Vec<_>>();
|
||||
let mut vec_par = SET.par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = [&'a', &'b', &'c', &'d', &'e', &'f'];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_into_iter_empty() {
|
||||
let vec_seq = SET_EMPTY.clone().into_iter().collect::<Vec<_>>();
|
||||
let vec_par = SET_EMPTY.clone().into_par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_into_iter() {
|
||||
let mut vec_seq = SET.clone().into_iter().collect::<Vec<_>>();
|
||||
let mut vec_par = SET.clone().into_par_iter().collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(vec_seq, vec_par);
|
||||
|
||||
// Do not depend on the exact order of values
|
||||
let expected_sorted = ['a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
vec_seq.sort_unstable();
|
||||
vec_par.sort_unstable();
|
||||
|
||||
assert_eq3!(vec_seq, vec_par, expected_sorted);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SET_VEC_EMPTY: Vec<char> = vec![];
|
||||
static ref SET_VEC: Vec<char> = vec!['b', 'a', 'c', 'e', 'f', 'd',];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_collect_empty() {
|
||||
let set_expected = SET_EMPTY.clone();
|
||||
let set_seq = SET_VEC_EMPTY.clone().into_iter().collect::<HashSet<_>>();
|
||||
let set_par = SET_VEC_EMPTY
|
||||
.clone()
|
||||
.into_par_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(set_seq, set_par);
|
||||
assert_eq!(set_seq, set_expected);
|
||||
assert_eq!(set_par, set_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_collect() {
|
||||
let set_expected = SET.clone();
|
||||
let set_seq = SET_VEC.clone().into_iter().collect::<HashSet<_>>();
|
||||
let set_par = SET_VEC.clone().into_par_iter().collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(set_seq, set_par);
|
||||
assert_eq!(set_seq, set_expected);
|
||||
assert_eq!(set_par, set_expected);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SET_EXISTING_EMPTY: HashSet<char> = HashSet::new();
|
||||
static ref SET_EXISTING: HashSet<char> = {
|
||||
let mut s = HashSet::new();
|
||||
s.insert('b');
|
||||
s.insert('a');
|
||||
s
|
||||
};
|
||||
static ref SET_EXTENSION_EMPTY: Vec<char> = vec![];
|
||||
static ref SET_EXTENSION: Vec<char> = vec!['c', 'e', 'f', 'd',];
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_existing_empty_extend_empty() {
|
||||
let expected = HashSet::new();
|
||||
let mut set_seq = SET_EXISTING_EMPTY.clone();
|
||||
let mut set_par = SET_EXISTING_EMPTY.clone();
|
||||
|
||||
set_seq.extend(SET_EXTENSION_EMPTY.iter().copied());
|
||||
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().copied());
|
||||
|
||||
assert_eq3!(set_seq, set_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_existing_empty_extend() {
|
||||
let expected = SET_EXTENSION.iter().copied().collect::<HashSet<_>>();
|
||||
let mut set_seq = SET_EXISTING_EMPTY.clone();
|
||||
let mut set_par = SET_EXISTING_EMPTY.clone();
|
||||
|
||||
set_seq.extend(SET_EXTENSION.iter().copied());
|
||||
set_par.par_extend(SET_EXTENSION.par_iter().copied());
|
||||
|
||||
assert_eq3!(set_seq, set_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_existing_extend_empty() {
|
||||
let expected = SET_EXISTING.clone();
|
||||
let mut set_seq = SET_EXISTING.clone();
|
||||
let mut set_par = SET_EXISTING.clone();
|
||||
|
||||
set_seq.extend(SET_EXTENSION_EMPTY.iter().copied());
|
||||
set_par.par_extend(SET_EXTENSION_EMPTY.par_iter().copied());
|
||||
|
||||
assert_eq3!(set_seq, set_par, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_existing_extend() {
|
||||
let expected = SET.clone();
|
||||
let mut set_seq = SET_EXISTING.clone();
|
||||
let mut set_par = SET_EXISTING.clone();
|
||||
|
||||
set_seq.extend(SET_EXTENSION.iter().copied());
|
||||
set_par.par_extend(SET_EXTENSION.par_iter().copied());
|
||||
|
||||
assert_eq3!(set_seq, set_par, expected);
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SET_A: HashSet<char> = ['a', 'b', 'c', 'd'].iter().copied().collect();
|
||||
static ref SET_B: HashSet<char> = ['a', 'b', 'e', 'f'].iter().copied().collect();
|
||||
static ref SET_DIFF_AB: HashSet<char> = ['c', 'd'].iter().copied().collect();
|
||||
static ref SET_DIFF_BA: HashSet<char> = ['e', 'f'].iter().copied().collect();
|
||||
static ref SET_SYMM_DIFF_AB: HashSet<char> = ['c', 'd', 'e', 'f'].iter().copied().collect();
|
||||
static ref SET_INTERSECTION_AB: HashSet<char> = ['a', 'b'].iter().copied().collect();
|
||||
static ref SET_UNION_AB: HashSet<char> =
|
||||
['a', 'b', 'c', 'd', 'e', 'f'].iter().copied().collect();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_difference() {
|
||||
let diff_ab_seq = SET_A.difference(&*SET_B).copied().collect::<HashSet<_>>();
|
||||
let diff_ab_par = SET_A
|
||||
.par_difference(&*SET_B)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq3!(diff_ab_seq, diff_ab_par, *SET_DIFF_AB);
|
||||
|
||||
let diff_ba_seq = SET_B.difference(&*SET_A).copied().collect::<HashSet<_>>();
|
||||
let diff_ba_par = SET_B
|
||||
.par_difference(&*SET_A)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq3!(diff_ba_seq, diff_ba_par, *SET_DIFF_BA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_symmetric_difference() {
|
||||
let symm_diff_ab_seq = SET_A
|
||||
.symmetric_difference(&*SET_B)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
let symm_diff_ab_par = SET_A
|
||||
.par_symmetric_difference(&*SET_B)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq3!(symm_diff_ab_seq, symm_diff_ab_par, *SET_SYMM_DIFF_AB);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_intersection() {
|
||||
let intersection_ab_seq = SET_A.intersection(&*SET_B).copied().collect::<HashSet<_>>();
|
||||
let intersection_ab_par = SET_A
|
||||
.par_intersection(&*SET_B)
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq3!(
|
||||
intersection_ab_seq,
|
||||
intersection_ab_par,
|
||||
*SET_INTERSECTION_AB
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_seq_par_equivalence_union() {
|
||||
let union_ab_seq = SET_A.union(&*SET_B).copied().collect::<HashSet<_>>();
|
||||
let union_ab_par = SET_A.par_union(&*SET_B).copied().collect::<HashSet<_>>();
|
||||
|
||||
assert_eq3!(union_ab_seq, union_ab_par, *SET_UNION_AB);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#![cfg(feature = "serde")]
|
||||
|
||||
use core::hash::BuildHasherDefault;
|
||||
use fnv::FnvHasher;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use serde_test::{assert_tokens, Token};
|
||||
|
||||
// We use FnvHash for this test because we rely on the ordering
|
||||
type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
|
||||
type FnvHashSet<T> = HashSet<T, BuildHasherDefault<FnvHasher>>;
|
||||
|
||||
#[test]
|
||||
fn map_serde_tokens_empty() {
|
||||
let map = FnvHashMap::<char, u32>::default();
|
||||
|
||||
assert_tokens(&map, &[Token::Map { len: Some(0) }, Token::MapEnd]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_serde_tokens() {
|
||||
let mut map = FnvHashMap::default();
|
||||
map.insert('b', 20);
|
||||
map.insert('a', 10);
|
||||
map.insert('c', 30);
|
||||
|
||||
assert_tokens(
|
||||
&map,
|
||||
&[
|
||||
Token::Map { len: Some(3) },
|
||||
Token::Char('a'),
|
||||
Token::I32(10),
|
||||
Token::Char('c'),
|
||||
Token::I32(30),
|
||||
Token::Char('b'),
|
||||
Token::I32(20),
|
||||
Token::MapEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_serde_tokens_empty() {
|
||||
let set = FnvHashSet::<u32>::default();
|
||||
|
||||
assert_tokens(&set, &[Token::Seq { len: Some(0) }, Token::SeqEnd]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_serde_tokens() {
|
||||
let mut set = FnvHashSet::default();
|
||||
set.insert(20);
|
||||
set.insert(10);
|
||||
set.insert(30);
|
||||
|
||||
assert_tokens(
|
||||
&set,
|
||||
&[
|
||||
Token::Seq { len: Some(3) },
|
||||
Token::I32(30),
|
||||
Token::I32(20),
|
||||
Token::I32(10),
|
||||
Token::SeqEnd,
|
||||
],
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#![cfg(not(miri))] // FIXME: takes too long
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng};
|
||||
use std::iter;
|
||||
|
||||
#[test]
|
||||
fn test_hashset_insert_remove() {
|
||||
let mut m: HashSet<Vec<char>> = HashSet::new();
|
||||
let seed = u64::from_le_bytes(*b"testseed");
|
||||
|
||||
let rng = &mut SmallRng::seed_from_u64(seed);
|
||||
let tx: Vec<Vec<char>> = iter::repeat_with(|| {
|
||||
rng.sample_iter(&Alphanumeric)
|
||||
.take(32)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
})
|
||||
.take(4096)
|
||||
.collect();
|
||||
|
||||
// more readable with explicit `true` / `false`
|
||||
#[allow(clippy::bool_assert_comparison)]
|
||||
for _ in 0..32 {
|
||||
for x in &tx {
|
||||
assert_eq!(m.contains(x), false);
|
||||
assert_eq!(m.insert(x.clone()), true);
|
||||
}
|
||||
for (i, x) in tx.iter().enumerate() {
|
||||
println!("removing {} {:?}", i, x);
|
||||
assert_eq!(m.remove(x), true);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue