feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"0b08942ab0f22cbccff294d42881658bcbac6ca57689f9a3d6cee16eca93585f","CHANGELOG.md":"bc027bc493e90784b42132a1ec8917d0384769ba64fc866199f56633df5dacc3","Cargo.lock":"abf7eb4d7f2d2d3f2ad99f8290519745400a9c140595358e383fb2c247c9e1c0","Cargo.toml":"e5cbc66eeb71d7544bcbbda22065478c5ea270dd620fbcc6d9757e7b18485d47","Cargo.toml.orig":"9857fae8d6eb9d18862af1bc701664e84ba51d27bf9697c85485e68934b0bbf5","Cross.toml":"47aeebdfd782a5052346ce29bf750c225828e108aca723486c3839adba5d6901","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"ff8f68cb076caf8cefe7a6430d4ac086ce6af2ca8ce2c4e5a2004d4552ef52a2","README.md":"63da5e3fcf4da2921ae34639aad6f0921828670a19cb871cac7786546434857d","benches/bench.rs":"288e88b0e3021a781a0b49a2a8ca4c390b76a63fd05cd7b524b64a7e988b841a","benches/insert_unique_unchecked.rs":"dfe644035cad6ff13b280e3d72c9df2569491ecddc84f7c2502daf940814a63e","benches/set_ops.rs":"936cd15055c25d42aabc6bde738e3135da541520b40633c4fba940eb73e06f4d","benches/with_capacity.rs":"a017f770f47f7941ac4404c0f4f36d2118949c587ae807302137c321ca911494","clippy.toml":"7535949f908c6d9aea4f9a9f3a7625552c93fc29e963d059d40f4def9d77ea7b","src/control/bitmask.rs":"53dcdac056620f02c475b618d33e69f34152ea559d86bd4545546144f017baa0","src/control/group/generic.rs":"41163fb7e5b4749f46521a1b142d43205d3e7416c3a85219eb2001577225bf3b","src/control/group/lsx.rs":"99c308a2e07133dce18b88230fbb054d5d1edcf465b87ee9fb1f409eacc4f241","src/control/group/mod.rs":"03d5cacaf762fcb0c9878e9f56355c7c9d0935d688d01c5e0d12aa673e16795d","src/control/group/neon.rs":"bde7726d321bd036cb9001818f962784346b932087b9f655c85ab26667d55fbb","src/control/group/sse2.rs":"833da90ab3fd72d306d5b999de96a4b6b52aa1a16ee7273c89bd12c9ead3bc45","src/control/mod.rs":"83fede19e9c5a26fd2c7372e6cf92547d32ade4cd69fde8a0eaaeb1be6ddc2ba","src/control/tag.rs":"9c94ab42aad3918bd77ffafdeb253f7f773a8f98bf56e36b85ebdc7a11ef676b","src/external_trait_impls/mod.rs":"d69528827794524cfd9acbeacc1ac4f6131e3c7574311e6d919f818f65fbff07","src/external_trait_impls/rayon/helpers.rs":"f69637231d76d413bd37c608fafb372e1147f9e9c8be2ba24ec47c6d2f005548","src/external_trait_impls/rayon/map.rs":"e1e08653c6c3d2f0586638ab7baf082c06fdc5551b5852b0f9e73aa9484b4955","src/external_trait_impls/rayon/mod.rs":"126edc882501dddd25e442d9236508b5b386eb8c0a9f5d654f2dd081086c1616","src/external_trait_impls/rayon/raw.rs":"04012fb2e99648819b4bc0044107ed3cb94013e242b7865075c5bd9ebf1b6865","src/external_trait_impls/rayon/set.rs":"7539348ff7bc6e3cce6b3c019d62dc401eea0138c578fef729c2593e8ead1cfa","src/external_trait_impls/rayon/table.rs":"aebd92261f44aef2e4c13a80a566e0308655396a3cc6f973d330d2f5ba26fc45","src/external_trait_impls/serde.rs":"eb17886c8d8e156b3e4068d87b0eb4900464d5d0a71ffae472fee90c735aaa02","src/hasher.rs":"939bc4f8c9e2d7e7ace266a8c121360ab4b44ad0540cdee06951e0197d96bc3c","src/lib.rs":"3593e802f411f0af91fc18cc9b08ae56acd2a2b6a1b3bfd1a8daf7887aa661c0","src/macros.rs":"98a26b908fc0fbe6a58d008a317e550013d615eb3cc17a5054a573c62c1d74cb","src/map.rs":"2f42092439f327756317ea15a32983cfd29503add8a6ff9716b92e50c03dfd7b","src/raw/alloc.rs":"4a1ba016f0932495bcec6007674bf4043543084cd04f4fe9cd5796013296e857","src/raw/mod.rs":"c24d6b9c8a9456505fa7a5b7c42b85468e6dd76bd49af54d17696c16dec4d2e8","src/raw_entry.rs":"41f54fabf968b6ba19a6fbb41372f7e86e1ccd221622591816fe48bcc3797369","src/rustc_entry.rs":"9c189a957af1ec5ff3549d15176c1608ca1c68330604a50b2ea19316a5dd14e2","src/scopeguard.rs":"1a246e08a63c06cd8ad934bd7da229421bf804f991ae93cd7e242da27ca6c601","src/set.rs":"1a4f3a1bfc5cfb3707172a1fcf16fc9d2190d97562ad544f8fc581e43a4d57c0","src/table.rs":"3bd787d9fe89e1ec9e62d7f70441c6711d9978473b5d7569e91b9a13cbe2b3eb","src/util.rs":"c929f855589653727016bd8577db7ca234e7e0820f1f2dc0cfe49f13f5056168","tests/equivalent_trait.rs":"84faa3fe9d67c375d03fec81f0f1412c47862477d42e84e7d235258236338d5b","tests/hasher.rs":"fd06130f011660743202904221f3f7487d8d143d8903c73cd3a76d079ebbe9fb","tests/rayon.rs":"39cb24ab45fce8087bb54948715c8b6973ebfba1a325292b5b3cd9aab50b5fd2","tests/serde.rs":"6bac8054db722dd049901b37a6e006535bac30f425eb5cd91af19b5bc1dfe78e","tests/set.rs":"fd9ffc6f8a435f2fbcada826c33599d682d9f688f0f8dbb14fcd1e442d4c2647"},"package":"841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "1876e4f02708b93903d55ef598f68e82a826518f"
|
||||
},
|
||||
"path_in_vcs": ""
|
||||
}
|
||||
Vendored
+644
@@ -0,0 +1,644 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.16.1](https://github.com/rust-lang/hashbrown/compare/v0.16.0...v0.16.1) - 2025-11-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added `HashTable` methods related to the raw bucket index (#657)
|
||||
- Added `VacantEntryRef::insert_with_key` (#579)
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed specialization for `Copy` types (#662)
|
||||
- The `get_many_mut` family of methods have been renamed to `get_disjoint_mut`
|
||||
to match the standard library. The old names are still present for now, but
|
||||
deprecated. (#648)
|
||||
- Recognize and use over-sized allocations when using custom allocators. (#523)
|
||||
- Depend on `serde_core` instead of `serde`. (#649)
|
||||
- Optimized `collect` on rayon parallel iterators. (#652)
|
||||
|
||||
## [0.16.0](https://github.com/rust-lang/hashbrown/compare/v0.15.5...v0.16.0) - 2025-08-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Bump foldhash, the default hasher, to 0.2.0.
|
||||
- Replaced `DefaultHashBuilder` with a newtype wrapper around `foldhash` instead
|
||||
of re-exporting it directly.
|
||||
|
||||
## [0.15.5](https://github.com/rust-lang/hashbrown/compare/v0.15.4...v0.15.5) - 2025-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Entry::or_default_entry` and `Entry::or_insert_entry`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Re-implemented likely/unlikely with `#[cold]`
|
||||
|
||||
## [0.15.4](https://github.com/rust-lang/hashbrown/compare/v0.15.3...v0.15.4) - 2025-06-05
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed optional dependency on compiler-builtins. This only affects building as part of `std`.
|
||||
|
||||
## [0.15.3](https://github.com/rust-lang/hashbrown/compare/v0.15.2...v0.15.3) - 2025-04-29
|
||||
|
||||
### Added
|
||||
|
||||
- SIMD implementation for LoongArch (#592, requires nightly)
|
||||
|
||||
### Changed
|
||||
|
||||
- Optimized insertion path by avoiding an unnecessary `match_empty` (#607)
|
||||
- Increased minimum table size for small types (#615)
|
||||
- Dropped FnMut trait bounds from `ExtractIf` data structures (#616)
|
||||
- Relaxed constraint in `hash_map::EntryRef` insertion methods `K: From<&Q>` to &Q: `Into<K>` (#611)
|
||||
- Added allocator template argument for `rustc_iter` (#605)
|
||||
- The `allocator-api2/nightly` feature is no longer enabled by `hashbrown/nightly` (#606)
|
||||
|
||||
## [v0.15.2] - 2024-11-14
|
||||
|
||||
### Added
|
||||
|
||||
- Marked `const fn` constructors as `rustc_const_stable_indirect` when built as
|
||||
part of the standard library. (#586)
|
||||
|
||||
## [v0.15.1] - 2024-11-03
|
||||
|
||||
This release removes the `borsh` feature introduced in 0.15.0 because it was
|
||||
found to be incorrectly implemented. Users should use the `hashbrown` feature of
|
||||
the `borsh` crate instead which provides the same trait implementations.
|
||||
|
||||
## ~~[v0.15.0] - 2024-10-01~~
|
||||
|
||||
This release was _yanked_ due to a broken implementation of the `borsh` feature.
|
||||
|
||||
This update contains breaking changes that remove the `raw` API with the hope of
|
||||
centralising on the `HashTable` API in the future. You can follow the discussion
|
||||
and progress in #545 to discuss features you think should be added to this API
|
||||
that were previously only possible on the `raw` API.
|
||||
|
||||
### Added
|
||||
|
||||
- Added `borsh` feature with `BorshSerialize` and `BorshDeserialize` impls. (#525)
|
||||
- Added `Assign` impls for `HashSet` operators. (#529)
|
||||
- Added `Default` impls for iterator types. (#542)
|
||||
- Added `HashTable::iter_hash{,_mut}` methods. (#549)
|
||||
- Added `Hash{Table,Map,Set}::allocation_size` methods. (#553)
|
||||
- Implemented `Debug` and `FusedIterator` for all `HashTable` iterators. (#561)
|
||||
- Specialized `Iterator::fold` for all `HashTable` iterators. (#561)
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `hash_set::VacantEntry::insert` to return `OccupiedEntry`. (#495)
|
||||
- Improved`hash_set::Difference::size_hint` lower-bound. (#530)
|
||||
- Improved `HashSet::is_disjoint` performance. (#531)
|
||||
- `equivalent` feature is now enabled by default. (#532)
|
||||
- `HashSet` operators now return a set with the same allocator. (#529)
|
||||
- Changed the default hasher to foldhash. (#563)
|
||||
- `ahash` feature has been renamed to `default-hasher`. (#533)
|
||||
- Entry API has been reworked and several methods have been renamed. (#535)
|
||||
- `Hash{Map,Set}::insert_unique_unchecked` is now unsafe. (#556)
|
||||
- The signature of `get_many_mut` and related methods was changed. (#562)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed typos, stray backticks in docs. (#558, #560)
|
||||
|
||||
### Removed
|
||||
|
||||
- Raw entry API is now under `raw-entry` feature, to be eventually removed. (#534, #555)
|
||||
- Raw table API has been made private and the `raw` feature is removed;
|
||||
in the future, all code should be using the `HashTable` API instead. (#531, #546)
|
||||
- `rykv` feature was removed; this is now provided by the `rykv` crate instead. (#554)
|
||||
- `HashSet::get_or_insert_owned` was removed in favor of `get_or_insert_with`. (#555)
|
||||
|
||||
## [v0.14.5] - 2024-04-28
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed index calculation in panic guard of `clone_from_impl`. (#511)
|
||||
|
||||
## ~~[v0.14.4] - 2024-03-19~~
|
||||
|
||||
This release was _yanked_ due to a breaking change.
|
||||
|
||||
## [v0.14.3] - 2023-11-26
|
||||
|
||||
### Added
|
||||
|
||||
- Specialized `fold` implementation of iterators. (#480)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Avoid using unstable `ptr::invalid_mut` on nightly. (#481)
|
||||
|
||||
## [v0.14.2] - 2023-10-19
|
||||
|
||||
### Added
|
||||
|
||||
- `HashTable` type which provides a low-level but safe API with explicit hashing. (#466)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Disabled the use of NEON instructions on big-endian ARM. (#475)
|
||||
- Disabled the use of NEON instructions on Miri. (#476)
|
||||
|
||||
## [v0.14.1] - 2023-09-28
|
||||
|
||||
### Added
|
||||
|
||||
- Allow serializing `HashMap`s that use a custom allocator. (#449)
|
||||
|
||||
### Changed
|
||||
|
||||
- Use the `Equivalent` trait from the `equivalent` crate. (#442)
|
||||
- Slightly improved performance of table resizing. (#451)
|
||||
- Relaxed MSRV to 1.63.0. (#457)
|
||||
- Removed `Clone` requirement from custom allocators. (#468)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed custom allocators being leaked in some situations. (#439, #465)
|
||||
|
||||
## [v0.14.0] - 2023-06-01
|
||||
|
||||
### Added
|
||||
|
||||
- Support for `allocator-api2` crate
|
||||
for interfacing with custom allocators on stable. (#417)
|
||||
- Optimized implementation for ARM using NEON instructions. (#430)
|
||||
- Support for rkyv serialization. (#432)
|
||||
- `Equivalent` trait to look up values without `Borrow`. (#345)
|
||||
- `Hash{Map,Set}::raw_table_mut` is added which returns a mutable reference. (#404)
|
||||
- Fast path for `clear` on empty tables. (#428)
|
||||
|
||||
### Changed
|
||||
|
||||
- Optimized insertion to only perform a single lookup. (#277)
|
||||
- `DrainFilter` (`drain_filter`) has been renamed to `ExtractIf` and no longer drops remaining
|
||||
elements when the iterator is dropped. #(374)
|
||||
- Bumped MSRV to 1.64.0. (#431)
|
||||
- `{Map,Set}::raw_table` now returns an immutable reference. (#404)
|
||||
- `VacantEntry` and `OccupiedEntry` now use the default hasher if none is
|
||||
specified in generics. (#389)
|
||||
- `RawTable::data_start` now returns a `NonNull` to match `RawTable::data_end`. (#387)
|
||||
- `RawIter::{reflect_insert, reflect_remove}` are now unsafe. (#429)
|
||||
- `RawTable::find_potential` is renamed to `find_or_find_insert_slot` and returns an `InsertSlot`. (#429)
|
||||
- `RawTable::remove` now also returns an `InsertSlot`. (#429)
|
||||
- `InsertSlot` can be used to insert an element with `RawTable::insert_in_slot`. (#429)
|
||||
- `RawIterHash` no longer has a lifetime tied to that of the `RawTable`. (#427)
|
||||
- The trait bounds of `HashSet::raw_table` have been relaxed to not require `Eq + Hash`. (#423)
|
||||
- `EntryRef::and_replace_entry_with` and `OccupiedEntryRef::replace_entry_with`
|
||||
were changed to give a `&K` instead of a `&Q` to the closure.
|
||||
|
||||
### Removed
|
||||
|
||||
- Support for `bumpalo` as an allocator with custom wrapper.
|
||||
Use `allocator-api2` feature in `bumpalo` to use it as an allocator
|
||||
for `hashbrown` collections. (#417)
|
||||
|
||||
## [v0.13.2] - 2023-01-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added `#[inline(always)]` to `find_inner`. (#375)
|
||||
- Fixed `RawTable::allocation_info` for empty tables. (#376)
|
||||
|
||||
## [v0.13.1] - 2022-11-10
|
||||
|
||||
### Added
|
||||
|
||||
- Added `Equivalent` trait to customize key lookups. (#350)
|
||||
- Added support for 16-bit targets. (#368)
|
||||
- Added `RawTable::allocation_info` which provides information about the memory
|
||||
usage of a table. (#371)
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped MSRV to 1.61.0.
|
||||
- Upgraded to `ahash` 0.8. (#357)
|
||||
- Make `with_hasher_in` const. (#355)
|
||||
- The following methods have been removed from the `RawTable` API in favor of
|
||||
safer alternatives:
|
||||
- `RawTable::erase_no_drop` => Use `RawTable::erase` or `RawTable::remove` instead.
|
||||
- `Bucket::read` => Use `RawTable::remove` instead.
|
||||
- `Bucket::drop` => Use `RawTable::erase` instead.
|
||||
- `Bucket::write` => Use `Bucket::as_mut` instead.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure that `HashMap` allocations don't exceed `isize::MAX`. (#362)
|
||||
- Fixed issue with field retagging in scopeguard. (#359)
|
||||
|
||||
## [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.15.2...HEAD
|
||||
[v0.15.2]: https://github.com/rust-lang/hashbrown/compare/v0.15.1...v0.15.2
|
||||
[v0.15.1]: https://github.com/rust-lang/hashbrown/compare/v0.15.0...v0.15.1
|
||||
[v0.15.0]: https://github.com/rust-lang/hashbrown/compare/v0.14.5...v0.15.0
|
||||
[v0.14.5]: https://github.com/rust-lang/hashbrown/compare/v0.14.4...v0.14.5
|
||||
[v0.14.4]: https://github.com/rust-lang/hashbrown/compare/v0.14.3...v0.14.4
|
||||
[v0.14.3]: https://github.com/rust-lang/hashbrown/compare/v0.14.2...v0.14.3
|
||||
[v0.14.2]: https://github.com/rust-lang/hashbrown/compare/v0.14.1...v0.14.2
|
||||
[v0.14.1]: https://github.com/rust-lang/hashbrown/compare/v0.14.0...v0.14.1
|
||||
[v0.14.0]: https://github.com/rust-lang/hashbrown/compare/v0.13.2...v0.14.0
|
||||
[v0.13.2]: https://github.com/rust-lang/hashbrown/compare/v0.13.1...v0.13.2
|
||||
[v0.13.1]: https://github.com/rust-lang/hashbrown/compare/v0.12.3...v0.13.1
|
||||
[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
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"bumpalo",
|
||||
"equivalent",
|
||||
"fnv",
|
||||
"foldhash",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"rand",
|
||||
"rayon",
|
||||
"rustc-std-workspace-alloc",
|
||||
"rustc-std-workspace-core",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"serde_test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-std-workspace-alloc"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d441c3b2ebf55cebf796bfdc265d67fa09db17b7bb6bd4be75c509e1e8fec3"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-std-workspace-core"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9c45b374136f52f2d6311062c7146bff20fec063c3f5d46a410bd937746955"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
Vendored
+180
@@ -0,0 +1,180 @@
|
||||
# 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.65.0"
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
authors = ["Amanieu d'Antras <amanieu@gmail.com>"]
|
||||
build = false
|
||||
exclude = [
|
||||
".github",
|
||||
"/ci/*",
|
||||
]
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
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"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"nightly",
|
||||
"rayon",
|
||||
"serde",
|
||||
"raw-entry",
|
||||
]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"default-hasher",
|
||||
"inline-more",
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"raw-entry",
|
||||
]
|
||||
default-hasher = ["dep:foldhash"]
|
||||
inline-more = []
|
||||
nightly = [
|
||||
"foldhash?/nightly",
|
||||
"bumpalo/allocator_api",
|
||||
]
|
||||
raw-entry = []
|
||||
rustc-dep-of-std = [
|
||||
"nightly",
|
||||
"core",
|
||||
"alloc",
|
||||
"rustc-internal-api",
|
||||
]
|
||||
rustc-internal-api = []
|
||||
serde = [
|
||||
"dep:serde_core",
|
||||
"dep:serde",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "hashbrown"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[test]]
|
||||
name = "equivalent_trait"
|
||||
path = "tests/equivalent_trait.rs"
|
||||
|
||||
[[test]]
|
||||
name = "hasher"
|
||||
path = "tests/hasher.rs"
|
||||
|
||||
[[test]]
|
||||
name = "rayon"
|
||||
path = "tests/rayon.rs"
|
||||
|
||||
[[test]]
|
||||
name = "serde"
|
||||
path = "tests/serde.rs"
|
||||
|
||||
[[test]]
|
||||
name = "set"
|
||||
path = "tests/set.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
path = "benches/bench.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "insert_unique_unchecked"
|
||||
path = "benches/insert_unique_unchecked.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "set_ops"
|
||||
path = "benches/set_ops.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "with_capacity"
|
||||
path = "benches/with_capacity.rs"
|
||||
|
||||
[dependencies.alloc]
|
||||
version = "1.0.0"
|
||||
optional = true
|
||||
package = "rustc-std-workspace-alloc"
|
||||
|
||||
[dependencies.allocator-api2]
|
||||
version = "0.2.9"
|
||||
features = ["alloc"]
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.core]
|
||||
version = "1.0.0"
|
||||
optional = true
|
||||
package = "rustc-std-workspace-core"
|
||||
|
||||
[dependencies.equivalent]
|
||||
version = "1.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.foldhash]
|
||||
version = "0.2.0"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dependencies.rayon]
|
||||
version = "1.9.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde_core]
|
||||
version = "1.0.221"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[dev-dependencies.bumpalo]
|
||||
version = "3.13.0"
|
||||
features = ["allocator-api2"]
|
||||
|
||||
[dev-dependencies.fnv]
|
||||
version = "1.0.7"
|
||||
|
||||
[dev-dependencies.lazy_static]
|
||||
version = "1.4"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.9.0"
|
||||
features = ["small_rng"]
|
||||
|
||||
[dev-dependencies.rayon]
|
||||
version = "1.2"
|
||||
|
||||
[dev-dependencies.serde_test]
|
||||
version = "1.0"
|
||||
|
||||
[target."cfg(any())".dependencies.serde]
|
||||
version = "1.0.220"
|
||||
optional = true
|
||||
default-features = false
|
||||
|
||||
[target."cfg(unix)".dev-dependencies.libc]
|
||||
version = "0.2.155"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
# FIXME: Drop this config when cross is updated to support loongarch64-linux-gnu
|
||||
[target.loongarch64-unknown-linux-gnu]
|
||||
image = "ghcr.io/cross-rs/loongarch64-unknown-linux-gnu:edge"
|
||||
Vendored
+201
@@ -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.
|
||||
Vendored
+25
@@ -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.
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
hashbrown
|
||||
=========
|
||||
|
||||
[](https://github.com/rust-lang/hashbrown/actions)
|
||||
[](https://crates.io/crates/hashbrown)
|
||||
[](https://docs.rs/hashbrown)
|
||||
[](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 [foldhash](https://github.com/orlp/foldhash) as the default hasher, which is much faster than SipHash.
|
||||
However, foldhash 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.
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
hashbrown = "0.16"
|
||||
```
|
||||
|
||||
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.
|
||||
- `equivalent`: Allows comparisons to be customized with the `Equivalent` trait. (enabled by default)
|
||||
- `raw-entry`: Enables access to the deprecated `RawEntry` API.
|
||||
- `inline-more`: Adds inline hints to most functions, improving run-time performance at the cost
|
||||
of compilation time. (enabled by default)
|
||||
- `default-hasher`: Compiles with foldhash as default hasher. (enabled by default)
|
||||
- `allocator-api2`: Enables support for allocators that support `allocator-api2`. (enabled 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.
|
||||
Vendored
+423
@@ -0,0 +1,423 @@
|
||||
// This benchmark suite contains some benchmarks along a set of dimensions:
|
||||
// Hasher: std default (SipHash) and crate default (foldhash).
|
||||
// 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::DefaultHashBuilder;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use std::{
|
||||
collections::hash_map::RandomState,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
|
||||
const SIZE: usize = 1000;
|
||||
const OP_COUNT: usize = 500;
|
||||
|
||||
// The default hashmap when using this crate directly.
|
||||
type FoldHashMap<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)
|
||||
static 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_foldhash_serial:ident, $bench_std_serial:ident,
|
||||
$bench_foldhash_highbits:ident, $bench_std_highbits:ident,
|
||||
$bench_foldhash_random:ident, $bench_std_random:ident) => {
|
||||
$bench_macro!($bench_foldhash_serial, FoldHashMap, 0..);
|
||||
$bench_macro!($bench_std_serial, StdHashMap, 0..);
|
||||
$bench_macro!(
|
||||
$bench_foldhash_highbits,
|
||||
FoldHashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!(
|
||||
$bench_std_highbits,
|
||||
StdHashMap,
|
||||
(0..).map(usize::swap_bytes)
|
||||
);
|
||||
$bench_macro!($bench_foldhash_random, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($bench_std_random, StdHashMap, RandomKeys::new());
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bench_suite_2 {
|
||||
($bench_macro:ident,
|
||||
$name0:ident, $size0:literal, $name1:ident, $size1:literal, $name2:ident, $size2:literal,
|
||||
$name3:ident, $size3:literal, $name4:ident, $size4:literal, $name5:ident, $size5:literal,
|
||||
$name6:ident, $size6:literal, $name7:ident, $size7:literal) => {
|
||||
$bench_macro!($name0, $size0, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name1, $size1, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name2, $size2, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name3, $size3, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name4, $size4, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name5, $size5, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name6, $size6, FoldHashMap, RandomKeys::new());
|
||||
$bench_macro!($name7, $size7, FoldHashMap, 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_foldhash_serial,
|
||||
insert_std_serial,
|
||||
insert_foldhash_highbits,
|
||||
insert_std_highbits,
|
||||
insert_foldhash_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_foldhash_serial,
|
||||
grow_insert_std_serial,
|
||||
grow_insert_foldhash_highbits,
|
||||
grow_insert_std_highbits,
|
||||
grow_insert_foldhash_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_foldhash_serial,
|
||||
insert_erase_std_serial,
|
||||
insert_erase_foldhash_highbits,
|
||||
insert_erase_std_highbits,
|
||||
insert_erase_foldhash_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_foldhash_serial,
|
||||
lookup_std_serial,
|
||||
lookup_foldhash_highbits,
|
||||
lookup_std_highbits,
|
||||
lookup_foldhash_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_foldhash_serial,
|
||||
lookup_fail_std_serial,
|
||||
lookup_fail_foldhash_highbits,
|
||||
lookup_fail_std_highbits,
|
||||
lookup_fail_foldhash_random,
|
||||
lookup_fail_std_random
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup_load_factor {
|
||||
($name:ident, $size:literal, $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(OP_COUNT) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite_2!(
|
||||
bench_lookup_load_factor, // same capacity of 32768 * 0.875
|
||||
loadfactor_lookup_14500,
|
||||
14500,
|
||||
loadfactor_lookup_16500,
|
||||
16500,
|
||||
loadfactor_lookup_18500,
|
||||
18500,
|
||||
loadfactor_lookup_20500,
|
||||
20500,
|
||||
loadfactor_lookup_22500,
|
||||
22500,
|
||||
loadfactor_lookup_24500,
|
||||
24500,
|
||||
loadfactor_lookup_26500,
|
||||
26500,
|
||||
loadfactor_lookup_28500,
|
||||
28500
|
||||
);
|
||||
|
||||
macro_rules! bench_lookup_fail_load_factor {
|
||||
($name:ident, $size:literal, $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(OP_COUNT) {
|
||||
black_box(m.get(&i));
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_suite_2!(
|
||||
bench_lookup_fail_load_factor, // same capacity of 32768 * 0.875
|
||||
loadfactor_lookup_fail_14500,
|
||||
14500,
|
||||
loadfactor_lookup_fail_16500,
|
||||
16500,
|
||||
loadfactor_lookup_fail_18500,
|
||||
18500,
|
||||
loadfactor_lookup_fail_20500,
|
||||
20500,
|
||||
loadfactor_lookup_fail_22500,
|
||||
22500,
|
||||
loadfactor_lookup_fail_24500,
|
||||
24500,
|
||||
loadfactor_lookup_fail_26500,
|
||||
26500,
|
||||
loadfactor_lookup_fail_28500,
|
||||
28500
|
||||
);
|
||||
|
||||
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_foldhash_serial,
|
||||
iter_std_serial,
|
||||
iter_foldhash_highbits,
|
||||
iter_std_highbits,
|
||||
iter_foldhash_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..223 {
|
||||
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,34 @@
|
||||
//! 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 {
|
||||
unsafe {
|
||||
m.insert_unique_unchecked(k, k);
|
||||
}
|
||||
}
|
||||
m
|
||||
});
|
||||
}
|
||||
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
//! This file contains benchmarks for the ops traits implemented by HashSet.
|
||||
//! Each test is intended to have a defined larger and smaller set,
|
||||
//! but using a larger size for the "small" set works just as well.
|
||||
//!
|
||||
//! Each assigning test is done in the configuration that is faster. Cheating, I know.
|
||||
//! The exception to this is Sub, because there the result differs. So I made two benchmarks for Sub.
|
||||
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use test::Bencher;
|
||||
|
||||
/// The number of items to generate for the larger of the sets.
|
||||
const LARGE_SET_SIZE: usize = 1000;
|
||||
|
||||
/// The number of items to generate for the smaller of the sets.
|
||||
const SMALL_SET_SIZE: usize = 100;
|
||||
|
||||
/// The number of keys present in both sets.
|
||||
const OVERLAP: usize =
|
||||
[LARGE_SET_SIZE, SMALL_SET_SIZE][(LARGE_SET_SIZE < SMALL_SET_SIZE) as usize] / 2;
|
||||
|
||||
/// Creates a set containing end - start unique string elements.
|
||||
fn create_set(start: usize, end: usize) -> HashSet<String> {
|
||||
(start..end).map(|nr| format!("key{}", nr)).collect()
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_or(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| &large_set | &small_set)
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_and(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| &large_set & &small_set)
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_xor(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| &large_set ^ &small_set)
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_sub_large_small(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| &large_set - &small_set)
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_sub_small_large(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| &small_set - &large_set)
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_or_assign(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut set = large_set.clone();
|
||||
set |= &small_set;
|
||||
set
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_and_assign(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut set = small_set.clone();
|
||||
set &= &large_set;
|
||||
set
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_bit_xor_assign(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut set = large_set.clone();
|
||||
set ^= &small_set;
|
||||
set
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_sub_assign_large_small(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut set = large_set.clone();
|
||||
set -= &small_set;
|
||||
set
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn set_ops_sub_assign_small_large(b: &mut Bencher) {
|
||||
let large_set = create_set(0, LARGE_SET_SIZE);
|
||||
let small_set = create_set(
|
||||
LARGE_SET_SIZE - OVERLAP,
|
||||
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAP,
|
||||
);
|
||||
b.iter(|| {
|
||||
let mut set = small_set.clone();
|
||||
set -= &large_set;
|
||||
set
|
||||
});
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
type Map<K, V> = HashMap<K, V>;
|
||||
|
||||
macro_rules! bench_with_capacity {
|
||||
($name:ident, $cap:expr) => {
|
||||
#[bench]
|
||||
fn $name(b: &mut Bencher) {
|
||||
b.iter(|| {
|
||||
// Construct a new empty map with a given capacity and return it to avoid
|
||||
// being optimized away. Dropping it measures allocation + minimal setup.
|
||||
let m: Map<usize, usize> = Map::with_capacity($cap);
|
||||
black_box(m)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bench_with_capacity!(with_capacity_000000, 0);
|
||||
bench_with_capacity!(with_capacity_000001, 1);
|
||||
bench_with_capacity!(with_capacity_000003, 3);
|
||||
bench_with_capacity!(with_capacity_000007, 7);
|
||||
bench_with_capacity!(with_capacity_000008, 8);
|
||||
bench_with_capacity!(with_capacity_000016, 16);
|
||||
bench_with_capacity!(with_capacity_000032, 32);
|
||||
bench_with_capacity!(with_capacity_000064, 64);
|
||||
bench_with_capacity!(with_capacity_000128, 128);
|
||||
bench_with_capacity!(with_capacity_000256, 256);
|
||||
bench_with_capacity!(with_capacity_000512, 512);
|
||||
bench_with_capacity!(with_capacity_001024, 1024);
|
||||
bench_with_capacity!(with_capacity_004096, 4096);
|
||||
bench_with_capacity!(with_capacity_016384, 16384);
|
||||
bench_with_capacity!(with_capacity_065536, 65536);
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
doc-valid-idents = [ "CppCon", "SwissTable", "SipHash", "HashDoS" ]
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
use super::group::{
|
||||
BitMaskWord, NonZeroBitMaskWord, BITMASK_ITER_MASK, BITMASK_MASK, BITMASK_STRIDE,
|
||||
};
|
||||
|
||||
/// 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 with
|
||||
/// groups of 8 bits representing one element. If any of these bits are non-zero
|
||||
/// then this element is considered to true in the mask. 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.
|
||||
///
|
||||
/// To iterate over a bit mask, it must be converted to a form where only 1 bit
|
||||
/// is set per element. This is done by applying `BITMASK_ITER_MASK` on the
|
||||
/// mask bits.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct BitMask(pub(crate) BitMaskWord);
|
||||
|
||||
#[allow(clippy::use_self)]
|
||||
impl BitMask {
|
||||
/// Returns a new `BitMask` with all bits inverted.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn invert(self) -> Self {
|
||||
BitMask(self.0 ^ BITMASK_MASK)
|
||||
}
|
||||
|
||||
/// Returns a new `BitMask` with the lowest bit removed.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn remove_lowest_bit(self) -> Self {
|
||||
BitMask(self.0 & (self.0 - 1))
|
||||
}
|
||||
|
||||
/// Returns whether the `BitMask` has at least one set bit.
|
||||
#[inline]
|
||||
pub(crate) fn any_bit_set(self) -> bool {
|
||||
self.0 != 0
|
||||
}
|
||||
|
||||
/// Returns the first set bit in the `BitMask`, if there is one.
|
||||
#[inline]
|
||||
pub(crate) fn lowest_set_bit(self) -> Option<usize> {
|
||||
if let Some(nonzero) = NonZeroBitMaskWord::new(self.0) {
|
||||
Some(Self::nonzero_trailing_zeros(nonzero))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of trailing zeroes in the `BitMask`.
|
||||
#[inline]
|
||||
pub(crate) 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
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as above but takes a `NonZeroBitMaskWord`.
|
||||
#[inline]
|
||||
fn nonzero_trailing_zeros(nonzero: NonZeroBitMaskWord) -> usize {
|
||||
if cfg!(target_arch = "arm") && BITMASK_STRIDE % 8 == 0 {
|
||||
// SAFETY: A byte-swapped non-zero value is still non-zero.
|
||||
let swapped = unsafe { NonZeroBitMaskWord::new_unchecked(nonzero.get().swap_bytes()) };
|
||||
swapped.leading_zeros() as usize / BITMASK_STRIDE
|
||||
} else {
|
||||
nonzero.trailing_zeros() as usize / BITMASK_STRIDE
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of leading zeroes in the `BitMask`.
|
||||
#[inline]
|
||||
pub(crate) 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 {
|
||||
// A BitMask only requires each element (group of bits) to be non-zero.
|
||||
// However for iteration we need each element to only contain 1 bit.
|
||||
BitMaskIter(BitMask(self.0 & BITMASK_ITER_MASK))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the contents of a `BitMask`, returning the indices of set
|
||||
/// bits.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BitMaskIter(pub(crate) 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)
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
use super::super::{BitMask, Tag};
|
||||
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_if! {
|
||||
if #[cfg(any(
|
||||
target_pointer_width = "64",
|
||||
target_arch = "aarch64",
|
||||
target_arch = "x86_64",
|
||||
target_arch = "wasm32",
|
||||
))] {
|
||||
type GroupWord = u64;
|
||||
type NonZeroGroupWord = core::num::NonZeroU64;
|
||||
} else {
|
||||
type GroupWord = u32;
|
||||
type NonZeroGroupWord = core::num::NonZeroU32;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type BitMaskWord = GroupWord;
|
||||
pub(crate) type NonZeroBitMaskWord = NonZeroGroupWord;
|
||||
pub(crate) const BITMASK_STRIDE: usize = 8;
|
||||
// We only care about the highest bit of each tag for the mask.
|
||||
#[allow(clippy::cast_possible_truncation, clippy::unnecessary_cast)]
|
||||
pub(crate) const BITMASK_MASK: BitMaskWord = u64::from_ne_bytes([Tag::DELETED.0; 8]) as GroupWord;
|
||||
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;
|
||||
|
||||
/// Helper function to replicate a tag across a `GroupWord`.
|
||||
#[inline]
|
||||
fn repeat(tag: Tag) -> GroupWord {
|
||||
GroupWord::from_ne_bytes([tag.0; Group::WIDTH])
|
||||
}
|
||||
|
||||
/// Abstraction over a group of control tags which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a word-sized integer.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) 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(crate) const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty tags, suitable for use as the initial
|
||||
/// value for an empty hash table.
|
||||
///
|
||||
/// This is guaranteed to be aligned to the group size.
|
||||
#[inline]
|
||||
pub(crate) const fn static_empty() -> &'static [Tag; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedTags {
|
||||
_align: [Group; 0],
|
||||
tags: [Tag; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_TAGS: AlignedTags = AlignedTags {
|
||||
_align: [],
|
||||
tags: [Tag::EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_TAGS.tags
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
|
||||
Group(ptr::read_unaligned(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
Group(ptr::read(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of tags to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
ptr::write(ptr.cast(), self.0);
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which *may*
|
||||
/// have the given value.
|
||||
///
|
||||
/// This function may return a false positive in certain cases where
|
||||
/// the tag 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 tag).
|
||||
#[inline]
|
||||
pub(crate) fn match_tag(self, tag: Tag) -> BitMask {
|
||||
// This algorithm is derived from
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html##ValueInWord
|
||||
let cmp = self.0 ^ repeat(tag);
|
||||
BitMask((cmp.wrapping_sub(repeat(Tag(0x01))) & !cmp & repeat(Tag::DELETED)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty(self) -> BitMask {
|
||||
// If the high bit is set, then the tag 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(Tag::DELETED)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty_or_deleted(self) -> BitMask {
|
||||
// A tag is EMPTY or DELETED iff the high bit is set
|
||||
BitMask((self.0 & repeat(Tag::DELETED)).to_le())
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are full.
|
||||
#[inline]
|
||||
pub(crate) fn match_full(self) -> BitMask {
|
||||
self.match_empty_or_deleted().invert()
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all tags in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub(crate) 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(Tag::DELETED);
|
||||
Group(!full + (full >> 7))
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
use super::super::{BitMask, Tag};
|
||||
use core::mem;
|
||||
use core::num::NonZeroU16;
|
||||
|
||||
use core::arch::loongarch64::*;
|
||||
|
||||
pub(crate) type BitMaskWord = u16;
|
||||
pub(crate) type NonZeroBitMaskWord = NonZeroU16;
|
||||
pub(crate) const BITMASK_STRIDE: usize = 1;
|
||||
pub(crate) const BITMASK_MASK: BitMaskWord = 0xffff;
|
||||
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;
|
||||
|
||||
/// Abstraction over a group of control tags which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a 128-bit LSX value.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct Group(m128i);
|
||||
|
||||
// FIXME: https://github.com/rust-lang/rust-clippy/issues/3859
|
||||
#[allow(clippy::use_self)]
|
||||
impl Group {
|
||||
/// Number of bytes in the group.
|
||||
pub(crate) const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty tags, 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(crate) const fn static_empty() -> &'static [Tag; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedTags {
|
||||
_align: [Group; 0],
|
||||
tags: [Tag; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_TAGS: AlignedTags = AlignedTags {
|
||||
_align: [],
|
||||
tags: [Tag::EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_TAGS.tags
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
|
||||
Group(lsx_vld::<0>(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
Group(lsx_vld::<0>(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of tags to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
lsx_vst::<0>(self.0, ptr.cast());
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which have
|
||||
/// the given value.
|
||||
#[inline]
|
||||
pub(crate) fn match_tag(self, tag: Tag) -> BitMask {
|
||||
unsafe {
|
||||
let cmp = lsx_vseq_b(self.0, lsx_vreplgr2vr_b(tag.0 as i32));
|
||||
BitMask(lsx_vpickve2gr_hu::<0>(lsx_vmskltz_b(cmp)) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty(self) -> BitMask {
|
||||
unsafe {
|
||||
let cmp = lsx_vseqi_b::<{ Tag::EMPTY.0 as i8 as i32 }>(self.0);
|
||||
BitMask(lsx_vpickve2gr_hu::<0>(lsx_vmskltz_b(cmp)) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty_or_deleted(self) -> BitMask {
|
||||
unsafe {
|
||||
// A tag is EMPTY or DELETED iff the high bit is set
|
||||
BitMask(lsx_vpickve2gr_hu::<0>(lsx_vmskltz_b(self.0)) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are full.
|
||||
#[inline]
|
||||
pub(crate) fn match_full(&self) -> BitMask {
|
||||
unsafe {
|
||||
// A tag is EMPTY or DELETED iff the high bit is set
|
||||
BitMask(lsx_vpickve2gr_hu::<0>(lsx_vmskgez_b(self.0)) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all tags in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub(crate) 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 > tag = 1111_1111 (true) or 0000_0000 (false)
|
||||
// 1111_1111 | 1000_0000 = 1111_1111
|
||||
// 0000_0000 | 1000_0000 = 1000_0000
|
||||
unsafe {
|
||||
let zero = lsx_vreplgr2vr_b(0);
|
||||
let special = lsx_vslt_b(self.0, zero);
|
||||
Group(lsx_vor_v(special, lsx_vreplgr2vr_b(Tag::DELETED.0 as i32)))
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
cfg_if! {
|
||||
// Use the SSE2 implementation if possible: it allows us to scan 16 buckets
|
||||
// at once instead of 8. We don't bother with AVX since it would require
|
||||
// runtime dispatch and wouldn't gain us much anyways: the probability of
|
||||
// finding a match drops off drastically after the first few buckets.
|
||||
//
|
||||
// I attempted an implementation on ARM using NEON instructions, but it
|
||||
// turns out that most NEON instructions have multi-cycle latency, which in
|
||||
// the end outweighs any gains over the generic implementation.
|
||||
if #[cfg(all(
|
||||
target_feature = "sse2",
|
||||
any(target_arch = "x86", target_arch = "x86_64"),
|
||||
not(miri),
|
||||
))] {
|
||||
mod sse2;
|
||||
use sse2 as imp;
|
||||
} else if #[cfg(all(
|
||||
target_arch = "aarch64",
|
||||
target_feature = "neon",
|
||||
// NEON intrinsics are currently broken on big-endian targets.
|
||||
// See https://github.com/rust-lang/stdarch/issues/1484.
|
||||
target_endian = "little",
|
||||
not(miri),
|
||||
))] {
|
||||
mod neon;
|
||||
use neon as imp;
|
||||
} else if #[cfg(all(
|
||||
feature = "nightly",
|
||||
target_arch = "loongarch64",
|
||||
target_feature = "lsx",
|
||||
not(miri),
|
||||
))] {
|
||||
mod lsx;
|
||||
use lsx as imp;
|
||||
} else {
|
||||
mod generic;
|
||||
use generic as imp;
|
||||
}
|
||||
}
|
||||
pub(crate) use self::imp::Group;
|
||||
pub(super) use self::imp::{
|
||||
BitMaskWord, NonZeroBitMaskWord, BITMASK_ITER_MASK, BITMASK_MASK, BITMASK_STRIDE,
|
||||
};
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
use super::super::{BitMask, Tag};
|
||||
use core::arch::aarch64 as neon;
|
||||
use core::mem;
|
||||
use core::num::NonZeroU64;
|
||||
|
||||
pub(crate) type BitMaskWord = u64;
|
||||
pub(crate) type NonZeroBitMaskWord = NonZeroU64;
|
||||
pub(crate) const BITMASK_STRIDE: usize = 8;
|
||||
pub(crate) const BITMASK_MASK: BitMaskWord = !0;
|
||||
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = 0x8080_8080_8080_8080;
|
||||
|
||||
/// Abstraction over a group of control tags which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a 64-bit NEON value.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct Group(neon::uint8x8_t);
|
||||
|
||||
#[allow(clippy::use_self)]
|
||||
impl Group {
|
||||
/// Number of bytes in the group.
|
||||
pub(crate) const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty tags, suitable for use as the initial
|
||||
/// value for an empty hash table.
|
||||
///
|
||||
/// This is guaranteed to be aligned to the group size.
|
||||
#[inline]
|
||||
pub(crate) const fn static_empty() -> &'static [Tag; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedTags {
|
||||
_align: [Group; 0],
|
||||
tags: [Tag; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_TAGS: AlignedTags = AlignedTags {
|
||||
_align: [],
|
||||
tags: [Tag::EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_TAGS.tags
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
|
||||
Group(neon::vld1_u8(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
Group(neon::vld1_u8(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of tags to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
neon::vst1_u8(ptr.cast(), self.0);
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which *may*
|
||||
/// have the given value.
|
||||
#[inline]
|
||||
pub(crate) fn match_tag(self, tag: Tag) -> BitMask {
|
||||
unsafe {
|
||||
let cmp = neon::vceq_u8(self.0, neon::vdup_n_u8(tag.0));
|
||||
BitMask(neon::vget_lane_u64(neon::vreinterpret_u64_u8(cmp), 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty(self) -> BitMask {
|
||||
self.match_tag(Tag::EMPTY)
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty_or_deleted(self) -> BitMask {
|
||||
unsafe {
|
||||
let cmp = neon::vcltz_s8(neon::vreinterpret_s8_u8(self.0));
|
||||
BitMask(neon::vget_lane_u64(neon::vreinterpret_u64_u8(cmp), 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are full.
|
||||
#[inline]
|
||||
pub(crate) fn match_full(self) -> BitMask {
|
||||
unsafe {
|
||||
let cmp = neon::vcgez_s8(neon::vreinterpret_s8_u8(self.0));
|
||||
BitMask(neon::vget_lane_u64(neon::vreinterpret_u64_u8(cmp), 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all tags in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub(crate) 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 > tag = 1111_1111 (true) or 0000_0000 (false)
|
||||
// 1111_1111 | 1000_0000 = 1111_1111
|
||||
// 0000_0000 | 1000_0000 = 1000_0000
|
||||
unsafe {
|
||||
let special = neon::vcltz_s8(neon::vreinterpret_s8_u8(self.0));
|
||||
Group(neon::vorr_u8(special, neon::vdup_n_u8(0x80)))
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
use super::super::{BitMask, Tag};
|
||||
use core::mem;
|
||||
use core::num::NonZeroU16;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
use core::arch::x86;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use core::arch::x86_64 as x86;
|
||||
|
||||
pub(crate) type BitMaskWord = u16;
|
||||
pub(crate) type NonZeroBitMaskWord = NonZeroU16;
|
||||
pub(crate) const BITMASK_STRIDE: usize = 1;
|
||||
pub(crate) const BITMASK_MASK: BitMaskWord = 0xffff;
|
||||
pub(crate) const BITMASK_ITER_MASK: BitMaskWord = !0;
|
||||
|
||||
/// Abstraction over a group of control tags which can be scanned in
|
||||
/// parallel.
|
||||
///
|
||||
/// This implementation uses a 128-bit SSE value.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) 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(crate) const WIDTH: usize = mem::size_of::<Self>();
|
||||
|
||||
/// Returns a full group of empty tags, 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(crate) const fn static_empty() -> &'static [Tag; Group::WIDTH] {
|
||||
#[repr(C)]
|
||||
struct AlignedTags {
|
||||
_align: [Group; 0],
|
||||
tags: [Tag; Group::WIDTH],
|
||||
}
|
||||
const ALIGNED_TAGS: AlignedTags = AlignedTags {
|
||||
_align: [],
|
||||
tags: [Tag::EMPTY; Group::WIDTH],
|
||||
};
|
||||
&ALIGNED_TAGS.tags
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)] // unaligned load
|
||||
pub(crate) unsafe fn load(ptr: *const Tag) -> Self {
|
||||
Group(x86::_mm_loadu_si128(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Loads a group of tags starting at the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn load_aligned(ptr: *const Tag) -> Self {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
Group(x86::_mm_load_si128(ptr.cast()))
|
||||
}
|
||||
|
||||
/// Stores the group of tags to the given address, which must be
|
||||
/// aligned to `mem::align_of::<Group>()`.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_ptr_alignment)]
|
||||
pub(crate) unsafe fn store_aligned(self, ptr: *mut Tag) {
|
||||
debug_assert_eq!(ptr.align_offset(mem::align_of::<Self>()), 0);
|
||||
x86::_mm_store_si128(ptr.cast(), self.0);
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which have
|
||||
/// the given value.
|
||||
#[inline]
|
||||
pub(crate) fn match_tag(self, tag: Tag) -> BitMask {
|
||||
#[allow(
|
||||
clippy::cast_possible_wrap, // tag.0: Tag as i8
|
||||
// tag: 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(tag.0 as i8));
|
||||
BitMask(x86::_mm_movemask_epi8(cmp) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty(self) -> BitMask {
|
||||
self.match_tag(Tag::EMPTY)
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are
|
||||
/// `EMPTY` or `DELETED`.
|
||||
#[inline]
|
||||
pub(crate) fn match_empty_or_deleted(self) -> BitMask {
|
||||
#[allow(
|
||||
// tag: 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 tag is EMPTY or DELETED iff the high bit is set
|
||||
BitMask(x86::_mm_movemask_epi8(self.0) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `BitMask` indicating all tags in the group which are full.
|
||||
#[inline]
|
||||
pub(crate) fn match_full(&self) -> BitMask {
|
||||
self.match_empty_or_deleted().invert()
|
||||
}
|
||||
|
||||
/// Performs the following transformation on all tags in the group:
|
||||
/// - `EMPTY => EMPTY`
|
||||
/// - `DELETED => EMPTY`
|
||||
/// - `FULL => DELETED`
|
||||
#[inline]
|
||||
pub(crate) 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 > tag = 1111_1111 (true) or 0000_0000 (false)
|
||||
// 1111_1111 | 1000_0000 = 1111_1111
|
||||
// 0000_0000 | 1000_0000 = 1000_0000
|
||||
#[allow(
|
||||
clippy::cast_possible_wrap, // tag: Tag::DELETED.0 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(Tag::DELETED.0 as i8),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
mod bitmask;
|
||||
mod group;
|
||||
mod tag;
|
||||
|
||||
use self::bitmask::BitMask;
|
||||
pub(crate) use self::{
|
||||
bitmask::BitMaskIter,
|
||||
group::Group,
|
||||
tag::{Tag, TagSliceExt},
|
||||
};
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
use core::{fmt, mem};
|
||||
|
||||
/// Single tag in a control group.
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct Tag(pub(super) u8);
|
||||
impl Tag {
|
||||
/// Control tag value for an empty bucket.
|
||||
pub(crate) const EMPTY: Tag = Tag(0b1111_1111);
|
||||
|
||||
/// Control tag value for a deleted bucket.
|
||||
pub(crate) const DELETED: Tag = Tag(0b1000_0000);
|
||||
|
||||
/// Checks whether a control tag represents a full bucket (top bit is clear).
|
||||
#[inline]
|
||||
pub(crate) const fn is_full(self) -> bool {
|
||||
self.0 & 0x80 == 0
|
||||
}
|
||||
|
||||
/// Checks whether a control tag represents a special value (top bit is set).
|
||||
#[inline]
|
||||
pub(crate) const fn is_special(self) -> bool {
|
||||
self.0 & 0x80 != 0
|
||||
}
|
||||
|
||||
/// Checks whether a special control value is EMPTY (just check 1 bit).
|
||||
#[inline]
|
||||
pub(crate) const fn special_is_empty(self) -> bool {
|
||||
debug_assert!(self.is_special());
|
||||
self.0 & 0x01 != 0
|
||||
}
|
||||
|
||||
/// Creates a control tag representing a full bucket with the given hash.
|
||||
#[inline]
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub(crate) const fn full(hash: u64) -> Tag {
|
||||
// Constant for function that grabs the top 7 bits of the hash.
|
||||
const MIN_HASH_LEN: usize = if mem::size_of::<usize>() < mem::size_of::<u64>() {
|
||||
mem::size_of::<usize>()
|
||||
} else {
|
||||
mem::size_of::<u64>()
|
||||
};
|
||||
|
||||
// Grab the top 7 bits of the hash. While the hash is normally a full 64-bit
|
||||
// value, some hash functions (such as FxHash) produce a usize result
|
||||
// instead, which means that the top 32 bits are 0 on 32-bit platforms.
|
||||
// So we use MIN_HASH_LEN constant to handle this.
|
||||
let top7 = hash >> (MIN_HASH_LEN * 8 - 7);
|
||||
Tag((top7 & 0x7f) as u8) // truncation
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for Tag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_special() {
|
||||
if self.special_is_empty() {
|
||||
f.pad("EMPTY")
|
||||
} else {
|
||||
f.pad("DELETED")
|
||||
}
|
||||
} else {
|
||||
f.debug_tuple("full").field(&(self.0 & 0x7F)).finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension trait for slices of tags.
|
||||
pub(crate) trait TagSliceExt {
|
||||
/// Fills the control with the given tag.
|
||||
fn fill_tag(&mut self, tag: Tag);
|
||||
|
||||
/// Clears out the control.
|
||||
#[inline]
|
||||
fn fill_empty(&mut self) {
|
||||
self.fill_tag(Tag::EMPTY)
|
||||
}
|
||||
}
|
||||
impl TagSliceExt for [Tag] {
|
||||
#[inline]
|
||||
fn fill_tag(&mut self, tag: Tag) {
|
||||
// SAFETY: We have access to the entire slice, so, we can write to the entire slice.
|
||||
unsafe { self.as_mut_ptr().write_bytes(tag.0, self.len()) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#[cfg(feature = "rayon")]
|
||||
pub(crate) mod rayon;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
@@ -0,0 +1,13 @@
|
||||
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().collect_vec_list();
|
||||
|
||||
let len = list.iter().map(Vec::len).sum();
|
||||
(list, len)
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
//! 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 = Global> {
|
||||
inner: RawIntoParIter<(K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, A: Allocator + 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> 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 = Global> {
|
||||
inner: RawParDrain<'a, (K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Send, V: Send, A: Allocator + 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> 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> 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> 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 + 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 + 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> 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> 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,
|
||||
{
|
||||
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,
|
||||
{
|
||||
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,
|
||||
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 Droppable<'a> {
|
||||
k: usize,
|
||||
counter: &'a AtomicUsize,
|
||||
}
|
||||
|
||||
impl Droppable<'_> {
|
||||
fn new(k: usize, counter: &AtomicUsize) -> Droppable<'_> {
|
||||
counter.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
Droppable { k, counter }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Droppable<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.counter.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Droppable<'_> {
|
||||
fn clone(&self) -> Self {
|
||||
Droppable::new(self.k, self.counter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Droppable<'_> {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
self.k.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Droppable<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.k == other.k
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Droppable<'_> {}
|
||||
|
||||
#[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 = Droppable::new(i, &key);
|
||||
let d2 = Droppable::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(|(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 = Droppable::new(i, &key);
|
||||
let d2 = Droppable::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(|(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,5 @@
|
||||
mod helpers;
|
||||
pub(crate) mod map;
|
||||
pub(crate) mod raw;
|
||||
pub(crate) mod set;
|
||||
pub(crate) mod table;
|
||||
@@ -0,0 +1,230 @@
|
||||
use crate::raw::Bucket;
|
||||
use crate::raw::{Allocator, Global, RawIter, RawIterRange, RawTable};
|
||||
use crate::scopeguard::guard;
|
||||
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 = Global> {
|
||||
table: RawTable<T, A>,
|
||||
}
|
||||
|
||||
impl<T, A: Allocator> 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 + 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, ref alloc)) = *alloc {
|
||||
unsafe {
|
||||
alloc.deallocate(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 = 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> Send for RawParDrain<'_, T, A> {}
|
||||
|
||||
impl<T, A: Allocator> 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> 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> 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> 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 = Global> {
|
||||
inner: map::IntoParIter<T, (), A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + 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 = Global> {
|
||||
inner: map::ParDrain<'a, T, (), A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + 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 = 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 + 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 = 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 + 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 = 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 + 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 = 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 + 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 + 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 + 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 + 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> 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,
|
||||
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,249 @@
|
||||
//! Rayon extensions for `HashTable`.
|
||||
|
||||
use super::raw::{RawIntoParIter, RawParDrain, RawParIter};
|
||||
use crate::hash_table::HashTable;
|
||||
use crate::raw::{Allocator, Global};
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
use rayon::iter::plumbing::UnindexedConsumer;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
/// Parallel iterator over shared references to entries in a map.
|
||||
///
|
||||
/// This iterator is created by the [`par_iter`] method on [`HashTable`]
|
||||
/// (provided by the [`IntoParallelRefIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_iter`]: /hashbrown/struct.HashTable.html#method.par_iter
|
||||
/// [`HashTable`]: /hashbrown/struct.HashTable.html
|
||||
/// [`IntoParallelRefIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelRefIterator.html
|
||||
pub struct ParIter<'a, T> {
|
||||
inner: RawParIter<T>,
|
||||
marker: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Sync> ParallelIterator for ParIter<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
#[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() })
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for ParIter<'_, T> {
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for ParIter<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let iter = unsafe { self.inner.iter() }.map(|x| unsafe { x.as_ref() });
|
||||
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 [`HashTable`]
|
||||
/// (provided by the [`IntoParallelRefMutIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_iter_mut`]: /hashbrown/struct.HashTable.html#method.par_iter_mut
|
||||
/// [`HashTable`]: /hashbrown/struct.HashTable.html
|
||||
/// [`IntoParallelRefMutIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelRefMutIterator.html
|
||||
pub struct ParIterMut<'a, T> {
|
||||
inner: RawParIter<T>,
|
||||
marker: PhantomData<&'a mut T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Send> ParallelIterator for ParIterMut<'a, T> {
|
||||
type Item = &'a mut T;
|
||||
|
||||
#[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_mut() })
|
||||
.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> fmt::Debug for ParIterMut<'_, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParIter {
|
||||
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 [`HashTable`]
|
||||
/// (provided by the [`IntoParallelIterator`] trait).
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`into_par_iter`]: /hashbrown/struct.HashTable.html#method.into_par_iter
|
||||
/// [`HashTable`]: /hashbrown/struct.HashTable.html
|
||||
/// [`IntoParallelIterator`]: https://docs.rs/rayon/1.0/rayon/iter/trait.IntoParallelIterator.html
|
||||
pub struct IntoParIter<T, A: Allocator = Global> {
|
||||
inner: RawIntoParIter<T, A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Send> ParallelIterator for IntoParIter<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>,
|
||||
{
|
||||
self.inner.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, A: Allocator> fmt::Debug for IntoParIter<T, 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 [`HashTable`].
|
||||
/// See its documentation for more.
|
||||
///
|
||||
/// [`par_drain`]: /hashbrown/struct.HashTable.html#method.par_drain
|
||||
/// [`HashTable`]: /hashbrown/struct.HashTable.html
|
||||
pub struct ParDrain<'a, T, A: Allocator = Global> {
|
||||
inner: RawParDrain<'a, T, A>,
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Sync> ParallelIterator for ParDrain<'_, 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>,
|
||||
{
|
||||
self.inner.drive_unindexed(consumer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug, A: Allocator> fmt::Debug for ParDrain<'_, T, A> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ParIter {
|
||||
inner: unsafe { self.inner.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator> HashTable<T, A> {
|
||||
/// 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<'_, T, A> {
|
||||
ParDrain {
|
||||
inner: self.raw.par_drain(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send, A: Allocator + Send> IntoParallelIterator for HashTable<T, 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.raw.into_par_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Sync, A: Allocator> IntoParallelIterator for &'a HashTable<T, 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: unsafe { self.raw.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Send, A: Allocator> IntoParallelIterator for &'a mut HashTable<T, A> {
|
||||
type Item = &'a mut T;
|
||||
type Iter = ParIterMut<'a, T>;
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn into_par_iter(self) -> Self::Iter {
|
||||
ParIterMut {
|
||||
inner: unsafe { self.raw.par_iter() },
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_par_table {
|
||||
use alloc::vec::Vec;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::{hash_map::make_hash, hash_table::HashTable, DefaultHashBuilder};
|
||||
|
||||
#[test]
|
||||
fn test_iterate() {
|
||||
let hasher = DefaultHashBuilder::default();
|
||||
let mut a = HashTable::new();
|
||||
for i in 0..32 {
|
||||
a.insert_unique(make_hash(&hasher, &i), i, |x| make_hash(&hasher, x));
|
||||
}
|
||||
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_move_iter() {
|
||||
let hasher = DefaultHashBuilder::default();
|
||||
let hs = {
|
||||
let mut hs = HashTable::new();
|
||||
|
||||
hs.insert_unique(make_hash(&hasher, &'a'), 'a', |x| make_hash(&hasher, x));
|
||||
hs.insert_unique(make_hash(&hasher, &'b'), 'b', |x| make_hash(&hasher, x));
|
||||
|
||||
hs
|
||||
};
|
||||
|
||||
let v = hs.into_par_iter().collect::<Vec<char>>();
|
||||
assert!(v == ['a', 'b'] || v == ['b', 'a']);
|
||||
}
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
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 crate::raw::Allocator;
|
||||
use core::fmt;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::marker::PhantomData;
|
||||
use serde_core::de::{Deserialize, Deserializer, MapAccess, Visitor};
|
||||
use serde_core::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::hash_map::HashMap;
|
||||
|
||||
use super::size_hint;
|
||||
|
||||
impl<K, V, H, A> Serialize for HashMap<K, V, H, A>
|
||||
where
|
||||
K: Serialize + Eq + Hash,
|
||||
V: Serialize,
|
||||
H: BuildHasher,
|
||||
A: Allocator,
|
||||
{
|
||||
#[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, A> Deserialize<'de> for HashMap<K, V, S, A>
|
||||
where
|
||||
K: Deserialize<'de> + Eq + Hash,
|
||||
V: Deserialize<'de>,
|
||||
S: BuildHasher + Default,
|
||||
A: Allocator + Default,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct MapVisitor<K, V, S, A>
|
||||
where
|
||||
A: Allocator,
|
||||
{
|
||||
marker: PhantomData<HashMap<K, V, S, A>>,
|
||||
}
|
||||
|
||||
impl<'de, K, V, S, A> Visitor<'de> for MapVisitor<K, V, S, A>
|
||||
where
|
||||
K: Deserialize<'de> + Eq + Hash,
|
||||
V: Deserialize<'de>,
|
||||
S: BuildHasher + Default,
|
||||
A: Allocator + Default,
|
||||
{
|
||||
type Value = HashMap<K, V, S, A>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a map")
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
let mut values = HashMap::with_capacity_and_hasher_in(
|
||||
size_hint::cautious(map.size_hint()),
|
||||
S::default(),
|
||||
A::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 crate::raw::Allocator;
|
||||
use core::fmt;
|
||||
use core::hash::{BuildHasher, Hash};
|
||||
use core::marker::PhantomData;
|
||||
use serde_core::de::{Deserialize, Deserializer, SeqAccess, Visitor};
|
||||
use serde_core::ser::{Serialize, Serializer};
|
||||
|
||||
use crate::hash_set::HashSet;
|
||||
|
||||
use super::size_hint;
|
||||
|
||||
impl<T, H, A> Serialize for HashSet<T, H, A>
|
||||
where
|
||||
T: Serialize + Eq + Hash,
|
||||
H: BuildHasher,
|
||||
A: Allocator,
|
||||
{
|
||||
#[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, A> Deserialize<'de> for HashSet<T, S, A>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
A: Allocator + Default,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct SeqVisitor<T, S, A>
|
||||
where
|
||||
A: Allocator,
|
||||
{
|
||||
marker: PhantomData<HashSet<T, S, A>>,
|
||||
}
|
||||
|
||||
impl<'de, T, S, A> Visitor<'de> for SeqVisitor<T, S, A>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
A: Allocator + Default,
|
||||
{
|
||||
type Value = HashSet<T, S, A>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("a sequence")
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "inline-more", inline)]
|
||||
fn visit_seq<M>(self, mut seq: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: SeqAccess<'de>,
|
||||
{
|
||||
let mut values = HashSet::with_capacity_and_hasher_in(
|
||||
size_hint::cautious(seq.size_hint()),
|
||||
S::default(),
|
||||
A::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>(&'a mut HashSet<T, S, A>)
|
||||
where
|
||||
A: Allocator;
|
||||
|
||||
impl<'de, T, S, A> Visitor<'de> for SeqInPlaceVisitor<'_, T, S, A>
|
||||
where
|
||||
T: Deserialize<'de> + Eq + Hash,
|
||||
S: BuildHasher + Default,
|
||||
A: Allocator,
|
||||
{
|
||||
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<M>(self, mut seq: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
#[cfg(feature = "default-hasher")]
|
||||
use {
|
||||
core::hash::{BuildHasher, Hasher},
|
||||
foldhash::fast::RandomState,
|
||||
};
|
||||
|
||||
/// Default hash builder for the `S` type parameter of
|
||||
/// [`HashMap`](crate::HashMap) and [`HashSet`](crate::HashSet).
|
||||
///
|
||||
/// This only implements `BuildHasher` when the "default-hasher" crate feature
|
||||
/// is enabled; otherwise it just serves as a placeholder, and a custom `S` type
|
||||
/// must be used to have a fully functional `HashMap` or `HashSet`.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DefaultHashBuilder {
|
||||
#[cfg(feature = "default-hasher")]
|
||||
inner: RandomState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-hasher")]
|
||||
impl BuildHasher for DefaultHashBuilder {
|
||||
type Hasher = DefaultHasher;
|
||||
|
||||
#[inline(always)]
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
DefaultHasher {
|
||||
inner: self.inner.build_hasher(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Default hasher for [`HashMap`](crate::HashMap) and [`HashSet`](crate::HashSet).
|
||||
#[cfg(feature = "default-hasher")]
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultHasher {
|
||||
inner: <RandomState as BuildHasher>::Hasher,
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-hasher")]
|
||||
macro_rules! forward_writes {
|
||||
($( $write:ident ( $ty:ty ) , )*) => {$(
|
||||
#[inline(always)]
|
||||
fn $write(&mut self, arg: $ty) {
|
||||
self.inner.$write(arg);
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
#[cfg(feature = "default-hasher")]
|
||||
impl Hasher for DefaultHasher {
|
||||
forward_writes! {
|
||||
write(&[u8]),
|
||||
write_u8(u8),
|
||||
write_u16(u16),
|
||||
write_u32(u32),
|
||||
write_u64(u64),
|
||||
write_u128(u128),
|
||||
write_usize(usize),
|
||||
write_i8(i8),
|
||||
write_i16(i16),
|
||||
write_i32(i32),
|
||||
write_i64(i64),
|
||||
write_i128(i128),
|
||||
write_isize(isize),
|
||||
}
|
||||
|
||||
// feature(hasher_prefixfree_extras)
|
||||
#[cfg(feature = "nightly")]
|
||||
forward_writes! {
|
||||
write_length_prefix(usize),
|
||||
write_str(&str),
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn finish(&self) -> u64 {
|
||||
self.inner.finish()
|
||||
}
|
||||
}
|
||||
Vendored
+190
@@ -0,0 +1,190 @@
|
||||
//! 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,
|
||||
trivial_clone,
|
||||
extend_one,
|
||||
allocator_api,
|
||||
slice_ptr_get,
|
||||
maybe_uninit_array_assume_init,
|
||||
strict_provenance_lints
|
||||
)
|
||||
)]
|
||||
#![cfg_attr(feature = "rustc-dep-of-std", feature(rustc_attrs))]
|
||||
#![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_attr(feature = "nightly", warn(fuzzy_provenance_casts))]
|
||||
#![cfg_attr(
|
||||
feature = "nightly",
|
||||
allow(clippy::incompatible_msrv, internal_features)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
all(feature = "nightly", target_arch = "loongarch64"),
|
||||
feature(stdarch_loongarch)
|
||||
)]
|
||||
#![cfg_attr(
|
||||
all(feature = "nightly", feature = "default-hasher"),
|
||||
feature(hasher_prefixfree_extras)
|
||||
)]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[cfg_attr(test, macro_use)]
|
||||
#[cfg_attr(feature = "rustc-dep-of-std", allow(unused_extern_crates))]
|
||||
extern crate alloc;
|
||||
|
||||
#[doc = include_str!("../README.md")]
|
||||
#[cfg(doctest)]
|
||||
pub struct ReadmeDoctests;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod control;
|
||||
mod hasher;
|
||||
mod raw;
|
||||
mod util;
|
||||
|
||||
mod external_trait_impls;
|
||||
mod map;
|
||||
#[cfg(feature = "raw-entry")]
|
||||
mod raw_entry;
|
||||
#[cfg(feature = "rustc-internal-api")]
|
||||
mod rustc_entry;
|
||||
mod scopeguard;
|
||||
mod set;
|
||||
mod table;
|
||||
|
||||
pub use crate::hasher::DefaultHashBuilder;
|
||||
#[cfg(feature = "default-hasher")]
|
||||
pub use crate::hasher::DefaultHasher;
|
||||
|
||||
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 mod hash_table {
|
||||
//! A hash table implemented with quadratic probing and SIMD lookup.
|
||||
pub use crate::table::*;
|
||||
|
||||
#[cfg(feature = "rayon")]
|
||||
/// [rayon]-based parallel iterator types for hash tables.
|
||||
/// You will rarely need to interact with it directly unless you have need
|
||||
/// to name one of the iterator types.
|
||||
///
|
||||
/// [rayon]: https://docs.rs/rayon/1.0/rayon
|
||||
pub mod rayon {
|
||||
pub use crate::external_trait_impls::rayon::table::*;
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::map::HashMap;
|
||||
pub use crate::set::HashSet;
|
||||
pub use crate::table::HashTable;
|
||||
|
||||
#[cfg(feature = "equivalent")]
|
||||
pub use equivalent::Equivalent;
|
||||
|
||||
// This is only used as a fallback when building as part of `std`.
|
||||
#[cfg(not(feature = "equivalent"))]
|
||||
/// Key equivalence trait.
|
||||
///
|
||||
/// This trait defines the function used to compare the input value with the
|
||||
/// map keys (or set values) during a lookup operation such as [`HashMap::get`]
|
||||
/// or [`HashSet::contains`].
|
||||
/// It is provided with a blanket implementation based on the
|
||||
/// [`Borrow`](core::borrow::Borrow) trait.
|
||||
///
|
||||
/// # Correctness
|
||||
///
|
||||
/// Equivalent values must hash to the same value.
|
||||
pub trait Equivalent<K: ?Sized> {
|
||||
/// Checks if this value is equivalent to the given key.
|
||||
///
|
||||
/// Returns `true` if both values are equivalent, and `false` otherwise.
|
||||
///
|
||||
/// # Correctness
|
||||
///
|
||||
/// When this function returns `true`, both `self` and `key` must hash to
|
||||
/// the same value.
|
||||
fn equivalent(&self, key: &K) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "equivalent"))]
|
||||
impl<Q: ?Sized, K: ?Sized> Equivalent<K> for Q
|
||||
where
|
||||
Q: Eq,
|
||||
K: core::borrow::Borrow<Q>,
|
||||
{
|
||||
fn equivalent(&self, key: &K) -> bool {
|
||||
self == key.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
},
|
||||
}
|
||||
Vendored
+70
@@ -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 appropriate #[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)*
|
||||
}
|
||||
}
|
||||
Vendored
+6815
File diff suppressed because it is too large.
Load diff
Vendored
+103
@@ -0,0 +1,103 @@
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::inner::AllocError;
|
||||
pub(crate) use self::inner::{do_alloc, Allocator, Global};
|
||||
|
||||
// Nightly-case.
|
||||
// Use unstable `allocator_api` feature.
|
||||
// This is compatible with `allocator-api2` which can be enabled or not.
|
||||
// This is used when building for `std`.
|
||||
#[cfg(feature = "nightly")]
|
||||
mod inner {
|
||||
#[cfg(test)]
|
||||
pub use crate::alloc::alloc::AllocError;
|
||||
use crate::alloc::alloc::Layout;
|
||||
pub use crate::alloc::alloc::{Allocator, Global};
|
||||
use core::ptr::NonNull;
|
||||
|
||||
#[allow(clippy::map_err_ignore)]
|
||||
pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
|
||||
match alloc.allocate(layout) {
|
||||
Ok(ptr) => Ok(ptr),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic non-nightly case.
|
||||
// This uses `allocator-api2` enabled by default.
|
||||
// If any crate enables "nightly" in `allocator-api2`,
|
||||
// this will be equivalent to the nightly case,
|
||||
// since `allocator_api2::alloc::Allocator` would be re-export of
|
||||
// `core::alloc::Allocator`.
|
||||
#[cfg(all(not(feature = "nightly"), feature = "allocator-api2"))]
|
||||
mod inner {
|
||||
use crate::alloc::alloc::Layout;
|
||||
#[cfg(test)]
|
||||
pub use allocator_api2::alloc::AllocError;
|
||||
pub use allocator_api2::alloc::{Allocator, Global};
|
||||
use core::ptr::NonNull;
|
||||
|
||||
#[allow(clippy::map_err_ignore)]
|
||||
pub(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
|
||||
match alloc.allocate(layout) {
|
||||
Ok(ptr) => Ok(ptr),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No-defaults case.
|
||||
// When building with default-features turned off and
|
||||
// neither `nightly` nor `allocator-api2` is enabled,
|
||||
// this will be used.
|
||||
// Making it impossible to use any custom allocator with collections defined
|
||||
// in this crate.
|
||||
// Any crate in build-tree can enable `allocator-api2`,
|
||||
// or `nightly` without disturbing users that don't want to use it.
|
||||
#[cfg(not(any(feature = "nightly", feature = "allocator-api2")))]
|
||||
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]>, ()> {
|
||||
match unsafe { NonNull::new(alloc(layout)) } {
|
||||
Some(data) => {
|
||||
// SAFETY: this is NonNull::slice_from_raw_parts.
|
||||
Ok(unsafe {
|
||||
NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
|
||||
data.as_ptr(),
|
||||
layout.size(),
|
||||
))
|
||||
})
|
||||
}
|
||||
None => Err(()),
|
||||
}
|
||||
}
|
||||
#[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(crate) fn do_alloc<A: Allocator>(alloc: &A, layout: Layout) -> Result<NonNull<[u8]>, ()> {
|
||||
alloc.allocate(layout)
|
||||
}
|
||||
}
|
||||
Vendored
+4623
File diff suppressed because it is too large.
Load diff
Vendored
+1740
File diff suppressed because it is too large.
Load diff
Vendored
+567
@@ -0,0 +1,567 @@
|
||||
use self::RustcEntry::*;
|
||||
use crate::map::{make_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,
|
||||
{
|
||||
/// 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_hash(&self.hash_builder, &key);
|
||||
if let Some(elem) = self.table.find(hash, |q| q.0.eq(&key)) {
|
||||
RustcEntry::Occupied(RustcOccupiedEntry {
|
||||
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,
|
||||
{
|
||||
/// 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> 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,
|
||||
{
|
||||
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 + Send,
|
||||
{
|
||||
}
|
||||
unsafe impl<K, V, A> Sync for RustcOccupiedEntry<'_, K, V, A>
|
||||
where
|
||||
K: Sync,
|
||||
V: Sync,
|
||||
A: Allocator + Sync,
|
||||
{
|
||||
}
|
||||
|
||||
impl<K: Debug, V: Debug, A: Allocator> 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,
|
||||
{
|
||||
hash: u64,
|
||||
key: K,
|
||||
table: &'a mut RawTable<(K, V), A>,
|
||||
}
|
||||
|
||||
impl<K: Debug, V, A: Allocator> 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> 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> 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> 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).0 }
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V, A: Allocator> 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 {
|
||||
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, A: Allocator> IntoIter<K, V, A> {
|
||||
/// 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, A: Allocator> Drain<'_, K, V, A> {
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
// Extracted from the scopeguard crate
|
||||
use core::{
|
||||
mem::ManuallyDrop,
|
||||
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 out of a ManuallyDrop<Self>
|
||||
// Don't use mem::forget as that might invalidate value
|
||||
let guard = ManuallyDrop::new(guard);
|
||||
unsafe {
|
||||
let value = ptr::read(&guard.value);
|
||||
// read the closure so that it is dropped
|
||||
let _ = ptr::read(&guard.dropfn);
|
||||
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);
|
||||
}
|
||||
}
|
||||
Vendored
+3121
File diff suppressed because it is too large.
Load diff
Vendored
+2962
File diff suppressed because it is too large.
Load diff
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
// FIXME: Replace with `core::hint::{likely, unlikely}` once they are stable.
|
||||
#[cfg(feature = "nightly")]
|
||||
pub(crate) use core::intrinsics::{likely, unlikely};
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
fn cold_path() {}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn likely(b: bool) -> bool {
|
||||
if b {
|
||||
true
|
||||
} else {
|
||||
cold_path();
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
#[inline(always)]
|
||||
pub(crate) fn unlikely(b: bool) -> bool {
|
||||
if b {
|
||||
cold_path();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: use strict provenance functions once they are stable.
|
||||
// Implement it with a transmute for now.
|
||||
#[inline(always)]
|
||||
#[allow(clippy::useless_transmute)] // clippy is wrong, cast and transmute are different here
|
||||
pub(crate) fn invalid_mut<T>(addr: usize) -> *mut T {
|
||||
unsafe { core::mem::transmute(addr) }
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use hashbrown::Equivalent;
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct Pair<A, B>(pub A, pub B);
|
||||
|
||||
impl<A, B, C, D> PartialEq<(A, B)> for Pair<C, D>
|
||||
where
|
||||
C: PartialEq<A>,
|
||||
D: PartialEq<B>,
|
||||
{
|
||||
fn eq(&self, rhs: &(A, B)) -> bool {
|
||||
self.0 == rhs.0 && self.1 == rhs.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B, X> Equivalent<X> for Pair<A, B>
|
||||
where
|
||||
Pair<A, B>: PartialEq<X>,
|
||||
A: Hash + Eq,
|
||||
B: Hash + Eq,
|
||||
{
|
||||
fn equivalent(&self, other: &X) -> bool {
|
||||
*self == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lookup() {
|
||||
let s = String::from;
|
||||
let mut map = HashMap::new();
|
||||
map.insert((s("a"), s("b")), 1);
|
||||
map.insert((s("a"), s("x")), 2);
|
||||
|
||||
assert!(map.contains_key(&Pair("a", "b")));
|
||||
assert!(!map.contains_key(&Pair("b", "a")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_str() {
|
||||
let s = String::from;
|
||||
let mut map = HashMap::new();
|
||||
map.insert(s("a"), 1);
|
||||
map.insert(s("b"), 2);
|
||||
map.insert(s("x"), 3);
|
||||
map.insert(s("y"), 4);
|
||||
|
||||
assert!(map.contains_key("a"));
|
||||
assert!(!map.contains_key("z"));
|
||||
assert_eq!(map.remove("b"), Some(2));
|
||||
}
|
||||
Vendored
+65
@@ -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));
|
||||
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));
|
||||
}
|
||||
|
||||
/// Use hashbrown's default hasher.
|
||||
#[test]
|
||||
fn default() {
|
||||
check::<hashbrown::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
|
||||
}
|
||||
fn write(&mut self, _: &[u8]) {}
|
||||
}
|
||||
|
||||
check::<BuildHasherDefault<MaxHasher>>();
|
||||
}
|
||||
Vendored
+535
@@ -0,0 +1,535 @@
|
||||
#![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<_>>();
|
||||
|
||||
// Work around type inference failure introduced by rend dev-dependency.
|
||||
let empty: [char; 0] = [];
|
||||
assert_eq3!(vec_seq, vec_par, empty);
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
Vendored
+65
@@ -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,
|
||||
],
|
||||
);
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
#![cfg(not(miri))] // FIXME: takes too long
|
||||
|
||||
use hashbrown::HashSet;
|
||||
use rand::{distr::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user