From 2f0ab9e78faa63ee10137f161568469925f688b4 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Wed, 22 Jul 2020 20:21:00 -0400 Subject: [PATCH] Add support for amortized resizes --- Cargo.toml | 13 ++++++++++++- src/lib.rs | 15 +++++++++++---- src/map.rs | 47 ++++++++++++++++++++++++++++++--------------- src/map/core.rs | 38 ++++++++++++++++++++++++++---------- src/map/core/raw.rs | 9 +++++++++ src/rayon/map.rs | 27 +++++++++++++++++--------- src/rayon/mod.rs | 2 +- src/rayon/set.rs | 19 +++++++++++++----- src/set.rs | 29 ++++++++++++++++++---------- 9 files changed, 144 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d3aef2ad..45228ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,13 +34,20 @@ bench = false autocfg = "1" [dependencies] serde = { version = "1.0", optional = true, default-features = false } -rayon = { version = "1.0", optional = true } +rayon_ = { version = "1.0", optional = true, package = "rayon" } +atone = { version = "0.3.1", optional = true } [dependencies.hashbrown] version = "0.8.1" default-features = false features = ["raw"] +[dependencies.griddle] +version = "0.3.1" +default-features = false +features = ["raw"] +optional = true + [dev-dependencies] itertools = "0.9" rand = {version = "0.7", features = ["small_rng"] } @@ -52,6 +59,10 @@ fxhash = "0.2.1" [features] # Serialization with serde 1.0 serde-1 = ["serde"] +rayon = ["rayon_", "atone/rayon"] + +# Use griddle over hashbrown, and atone over Vec, for amortized resizes +amortize = ["griddle", "atone"] # for testing only, of course test_low_transition_point = [] diff --git a/src/lib.rs b/src/lib.rs index 2df2ceed..e0a1d072 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -86,9 +86,11 @@ extern crate alloc; extern crate std; #[cfg(not(has_std))] +#[cfg_attr(feature = "amortize", allow(unused_imports))] use alloc::vec::{self, Vec}; #[cfg(has_std)] +#[cfg_attr(feature = "amortize", allow(unused_imports))] use std::vec::{self, Vec}; #[macro_use] @@ -113,6 +115,11 @@ pub use crate::set::IndexSet; // shared private items +#[cfg(feature = "amortize")] +type EntryVec = atone::Vc; +#[cfg(not(feature = "amortize"))] +type EntryVec = Vec; + /// Hash value newtype. Not larger than usize, since anything larger /// isn't used for selecting position anyway. #[derive(Clone, Copy, Debug, PartialEq)] @@ -182,10 +189,10 @@ impl Bucket { trait Entries { type Entry; - fn into_entries(self) -> Vec; - fn as_entries(&self) -> &[Self::Entry]; - fn as_entries_mut(&mut self) -> &mut [Self::Entry]; + fn into_entries(self) -> EntryVec; + fn as_entries(&self) -> &EntryVec; + fn as_entries_mut(&mut self) -> &mut EntryVec; fn with_entries(&mut self, f: F) where - F: FnOnce(&mut [Self::Entry]); + F: FnOnce(&mut EntryVec); } diff --git a/src/map.rs b/src/map.rs index 4157c564..53cce684 100644 --- a/src/map.rs +++ b/src/map.rs @@ -8,13 +8,12 @@ pub use crate::mutable_keys::MutableKeys; #[cfg(feature = "rayon")] pub use crate::rayon::map as rayon; -use crate::vec::{self, Vec}; +use crate::EntryVec; use ::core::cmp::Ordering; use ::core::fmt; use ::core::hash::{BuildHasher, Hash, Hasher}; use ::core::iter::FromIterator; use ::core::ops::{Index, IndexMut, RangeFull}; -use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut}; #[cfg(has_std)] use std::collections::hash_map::RandomState; @@ -101,23 +100,23 @@ impl Entries for IndexMap { type Entry = Bucket; #[inline] - fn into_entries(self) -> Vec { + fn into_entries(self) -> EntryVec { self.core.into_entries() } #[inline] - fn as_entries(&self) -> &[Self::Entry] { + fn as_entries(&self) -> &EntryVec { self.core.as_entries() } #[inline] - fn as_entries_mut(&mut self) -> &mut [Self::Entry] { + fn as_entries_mut(&mut self) -> &mut EntryVec { self.core.as_entries_mut() } fn with_entries(&mut self, f: F) where - F: FnOnce(&mut [Self::Entry]), + F: FnOnce(&mut EntryVec), { self.core.with_entries(f); } @@ -618,6 +617,8 @@ where K: Ord, { self.with_entries(|entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.sort_by(|a, b| Ord::cmp(&a.key, &b.key)); }); } @@ -635,6 +636,8 @@ where F: FnMut(&K, &V, &K, &V) -> Ordering, { self.with_entries(move |entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); }); } @@ -648,7 +651,11 @@ where F: FnMut(&K, &V, &K, &V) -> Ordering, { let mut entries = self.into_entries(); - entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); + entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + } IntoIter { iter: entries.into_iter(), } @@ -724,7 +731,7 @@ impl IndexMap { /// [`keys`]: struct.IndexMap.html#method.keys /// [`IndexMap`]: struct.IndexMap.html pub struct Keys<'a, K, V> { - pub(crate) iter: SliceIter<'a, Bucket>, + pub(crate) iter: <&'a EntryVec> as IntoIterator>::IntoIter, } impl<'a, K, V> Iterator for Keys<'a, K, V> { @@ -768,7 +775,7 @@ impl<'a, K: fmt::Debug, V> fmt::Debug for Keys<'a, K, V> { /// [`values`]: struct.IndexMap.html#method.values /// [`IndexMap`]: struct.IndexMap.html pub struct Values<'a, K, V> { - iter: SliceIter<'a, Bucket>, + iter: <&'a EntryVec> as IntoIterator>::IntoIter, } impl<'a, K, V> Iterator for Values<'a, K, V> { @@ -812,7 +819,7 @@ impl<'a, K, V: fmt::Debug> fmt::Debug for Values<'a, K, V> { /// [`values_mut`]: struct.IndexMap.html#method.values_mut /// [`IndexMap`]: struct.IndexMap.html pub struct ValuesMut<'a, K, V> { - iter: SliceIterMut<'a, Bucket>, + iter: <&'a mut EntryVec> as IntoIterator>::IntoIter, } impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { @@ -841,7 +848,7 @@ impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> { /// [`iter`]: struct.IndexMap.html#method.iter /// [`IndexMap`]: struct.IndexMap.html pub struct Iter<'a, K, V> { - iter: SliceIter<'a, Bucket>, + iter: <&'a EntryVec> as IntoIterator>::IntoIter, } impl<'a, K, V> Iterator for Iter<'a, K, V> { @@ -885,7 +892,7 @@ impl<'a, K: fmt::Debug, V: fmt::Debug> fmt::Debug for Iter<'a, K, V> { /// [`iter_mut`]: struct.IndexMap.html#method.iter_mut /// [`IndexMap`]: struct.IndexMap.html pub struct IterMut<'a, K, V> { - iter: SliceIterMut<'a, Bucket>, + iter: <&'a mut EntryVec> as IntoIterator>::IntoIter, } impl<'a, K, V> Iterator for IterMut<'a, K, V> { @@ -914,7 +921,7 @@ impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { /// [`into_iter`]: struct.IndexMap.html#method.into_iter /// [`IndexMap`]: struct.IndexMap.html pub struct IntoIter { - pub(crate) iter: vec::IntoIter>, + pub(crate) iter: > as IntoIterator>::IntoIter, } impl Iterator for IntoIter { @@ -936,6 +943,11 @@ impl ExactSizeIterator for IntoIter { } impl fmt::Debug for IntoIter { + #[cfg(feature = "amortize")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntoIter").finish() + } + #[cfg(not(feature = "amortize"))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::refs); f.debug_list().entries(iter).finish() @@ -950,7 +962,10 @@ impl fmt::Debug for IntoIter { /// [`drain`]: struct.IndexMap.html#method.drain /// [`IndexMap`]: struct.IndexMap.html pub struct Drain<'a, K, V> { - pub(crate) iter: vec::Drain<'a, Bucket>, + #[cfg(not(feature = "amortize"))] + pub(crate) iter: crate::vec::Drain<'a, Bucket>, + #[cfg(feature = "amortize")] + pub(crate) iter: atone::vc::Drain<'a, Bucket>, } impl<'a, K, V> Iterator for Drain<'a, K, V> { @@ -1307,7 +1322,9 @@ mod tests { assert_eq!(map.get(&i), Some(&(i * i))); map.shrink_to_fit(); assert_eq!(map.len(), i + 1); - assert_eq!(map.capacity(), i + 1); + if !cfg!(feature = "amortize") { + assert_eq!(map.capacity(), i + 1); + } assert_eq!(map.get(&i), Some(&(i * i))); } } diff --git a/src/map/core.rs b/src/map/core.rs index 044bae73..c5301019 100644 --- a/src/map/core.rs +++ b/src/map/core.rs @@ -9,9 +9,16 @@ mod raw; +#[cfg(feature = "amortize")] +use griddle::raw::RawTable; +#[cfg(not(feature = "amortize"))] use hashbrown::raw::RawTable; -use crate::vec::{Drain, Vec}; +#[cfg(not(feature = "amortize"))] +use crate::vec::Drain; +#[cfg(feature = "amortize")] +use atone::vc::Drain; + use core::cmp; use core::fmt; use core::mem::replace; @@ -19,6 +26,7 @@ use core::ops::RangeFull; use crate::equivalent::Equivalent; use crate::util::enumerate; +use crate::EntryVec; use crate::{Bucket, Entries, HashValue}; /// Core of the map that does not depend on S @@ -26,11 +34,11 @@ pub(crate) struct IndexMapCore { /// indices mapping from the entry hash to its index. indices: RawTable, /// entries is a dense vec of entries in their order. - entries: Vec>, + entries: EntryVec>, } #[inline(always)] -fn get_hash(entries: &[Bucket]) -> impl Fn(&usize) -> u64 + '_ { +fn get_hash(entries: &EntryVec>) -> impl Fn(&usize) -> u64 + '_ { move |&i| entries[i].hash.get() } @@ -40,8 +48,14 @@ where V: Clone, { fn clone(&self) -> Self { + #[cfg(feature = "amortize")] + let indices = { + let hasher = get_hash(&self.entries); + self.indices.clone_with_hasher(hasher) + }; + #[cfg(not(feature = "amortize"))] let indices = self.indices.clone(); - let mut entries = Vec::with_capacity(indices.capacity()); + let mut entries = EntryVec::with_capacity(indices.capacity()); entries.clone_from(&self.entries); IndexMapCore { indices, entries } } @@ -74,23 +88,23 @@ impl Entries for IndexMapCore { type Entry = Bucket; #[inline] - fn into_entries(self) -> Vec { + fn into_entries(self) -> EntryVec { self.entries } #[inline] - fn as_entries(&self) -> &[Self::Entry] { + fn as_entries(&self) -> &EntryVec { &self.entries } #[inline] - fn as_entries_mut(&mut self) -> &mut [Self::Entry] { + fn as_entries_mut(&mut self) -> &mut EntryVec { &mut self.entries } fn with_entries(&mut self, f: F) where - F: FnOnce(&mut [Self::Entry]), + F: FnOnce(&mut EntryVec), { f(&mut self.entries); self.rebuild_hash_table(); @@ -102,7 +116,7 @@ impl IndexMapCore { pub(crate) fn new() -> Self { IndexMapCore { indices: RawTable::new(), - entries: Vec::new(), + entries: EntryVec::new(), } } @@ -110,7 +124,7 @@ impl IndexMapCore { pub(crate) fn with_capacity(n: usize) -> Self { IndexMapCore { indices: RawTable::with_capacity(n), - entries: Vec::with_capacity(n), + entries: EntryVec::with_capacity(n), } } @@ -218,7 +232,11 @@ impl IndexMapCore { debug_assert!(self.indices.capacity() >= self.entries.len()); for (i, entry) in enumerate(&self.entries) { // We should never have to reallocate, so there's no need for a real hasher. + #[cfg(not(feature = "amortize"))] self.indices.insert_no_grow(entry.hash.get(), i); + #[cfg(feature = "amortize")] + self.indices + .insert_no_grow(entry.hash.get(), i, |_| unreachable!()); } } } diff --git a/src/map/core/raw.rs b/src/map/core/raw.rs index fa075f01..f1acd10c 100644 --- a/src/map/core/raw.rs +++ b/src/map/core/raw.rs @@ -5,8 +5,14 @@ use super::{Entry, Equivalent, HashValue, IndexMapCore, VacantEntry}; use core::fmt; use core::mem::replace; +#[cfg(feature = "amortize")] +use griddle::raw::RawTable; +#[cfg(not(feature = "amortize"))] use hashbrown::raw::RawTable; +#[cfg(feature = "amortize")] +type RawBucket = griddle::raw::Bucket; +#[cfg(not(feature = "amortize"))] type RawBucket = hashbrown::raw::Bucket; pub(super) struct DebugIndices<'a>(pub &'a RawTable); @@ -105,6 +111,9 @@ impl IndexMapCore { // correct indices that point to the entries that followed the removed entry. // use a heuristic between a full sweep vs. a `find()` for every shifted item. let raw_capacity = self.indices.buckets(); + #[cfg(feature = "amortize")] + let shifted_entries = self.entries.range(index..); + #[cfg(not(feature = "amortize"))] let shifted_entries = &self.entries[index..]; if shifted_entries.len() > raw_capacity / 2 { // shift all indices greater than `index` diff --git a/src/rayon/map.rs b/src/rayon/map.rs index 417d2ee7..4a9f79b6 100644 --- a/src/rayon/map.rs +++ b/src/rayon/map.rs @@ -6,8 +6,8 @@ //! Requires crate feature `"rayon"` use super::collect; -use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; -use rayon::prelude::*; +use rayon_::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; +use rayon_::prelude::*; use crate::vec::Vec; use core::cmp::Ordering; @@ -16,6 +16,7 @@ use core::hash::{BuildHasher, Hash}; use crate::Bucket; use crate::Entries; +use crate::EntryVec; use crate::IndexMap; /// Requires crate feature `"rayon"`. @@ -43,7 +44,7 @@ where /// [`into_par_iter`]: ../struct.IndexMap.html#method.into_par_iter /// [`IndexMap`]: ../struct.IndexMap.html pub struct IntoParIter { - entries: Vec>, + entries: EntryVec>, } impl fmt::Debug for IntoParIter { @@ -88,7 +89,7 @@ where /// [`par_iter`]: ../struct.IndexMap.html#method.par_iter /// [`IndexMap`]: ../struct.IndexMap.html pub struct ParIter<'a, K, V> { - entries: &'a [Bucket], + entries: &'a EntryVec>, } impl<'a, K, V> Clone for ParIter<'a, K, V> { @@ -139,7 +140,7 @@ where /// [`par_iter_mut`]: ../struct.IndexMap.html#method.par_iter_mut /// [`IndexMap`]: ../struct.IndexMap.html pub struct ParIterMut<'a, K, V> { - entries: &'a mut [Bucket], + entries: &'a mut EntryVec>, } impl<'a, K: Sync + Send, V: Send> ParallelIterator for ParIterMut<'a, K, V> { @@ -206,7 +207,7 @@ where /// [`par_keys`]: ../struct.IndexMap.html#method.par_keys /// [`IndexMap`]: ../struct.IndexMap.html pub struct ParKeys<'a, K, V> { - entries: &'a [Bucket], + entries: &'a EntryVec>, } impl<'a, K, V> Clone for ParKeys<'a, K, V> { @@ -240,7 +241,7 @@ impl<'a, K: Sync, V: Sync> IndexedParallelIterator for ParKeys<'a, K, V> { /// [`par_values`]: ../struct.IndexMap.html#method.par_values /// [`IndexMap`]: ../struct.IndexMap.html pub struct ParValues<'a, K, V> { - entries: &'a [Bucket], + entries: &'a EntryVec>, } impl<'a, K, V> Clone for ParValues<'a, K, V> { @@ -289,6 +290,8 @@ where K: Ord, { self.with_entries(|entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.par_sort_by(|a, b| K::cmp(&a.key, &b.key)); }); } @@ -303,6 +306,8 @@ where F: Fn(&K, &V, &K, &V) -> Ordering + Sync, { self.with_entries(|entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.par_sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); }); } @@ -314,7 +319,11 @@ where F: Fn(&K, &V, &K, &V) -> Ordering + Sync, { let mut entries = self.into_entries(); - entries.par_sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); + entries.par_sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value)); + } IntoParIter { entries } } } @@ -327,7 +336,7 @@ where /// [`par_values_mut`]: ../struct.IndexMap.html#method.par_values_mut /// [`IndexMap`]: ../struct.IndexMap.html pub struct ParValuesMut<'a, K, V> { - entries: &'a mut [Bucket], + entries: &'a mut EntryVec>, } impl<'a, K: Send, V: Send> ParallelIterator for ParValuesMut<'a, K, V> { diff --git a/src/rayon/mod.rs b/src/rayon/mod.rs index 57c810be..32fd13f4 100644 --- a/src/rayon/mod.rs +++ b/src/rayon/mod.rs @@ -1,4 +1,4 @@ -use rayon::prelude::*; +use rayon_::prelude::*; #[cfg(not(has_std))] use alloc::collections::LinkedList; diff --git a/src/rayon/set.rs b/src/rayon/set.rs index af316bb5..51b1f47b 100644 --- a/src/rayon/set.rs +++ b/src/rayon/set.rs @@ -6,8 +6,8 @@ //! Requires crate feature `"rayon"`. use super::collect; -use rayon::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; -use rayon::prelude::*; +use rayon_::iter::plumbing::{Consumer, ProducerCallback, UnindexedConsumer}; +use rayon_::prelude::*; use crate::vec::Vec; use core::cmp::Ordering; @@ -15,6 +15,7 @@ use core::fmt; use core::hash::{BuildHasher, Hash}; use crate::Entries; +use crate::EntryVec; use crate::IndexSet; type Bucket = crate::Bucket; @@ -43,7 +44,7 @@ where /// [`IndexSet`]: ../struct.IndexSet.html /// [`into_par_iter`]: ../struct.IndexSet.html#method.into_par_iter pub struct IntoParIter { - entries: Vec>, + entries: EntryVec>, } impl fmt::Debug for IntoParIter { @@ -87,7 +88,7 @@ where /// [`IndexSet`]: ../struct.IndexSet.html /// [`par_iter`]: ../struct.IndexSet.html#method.par_iter pub struct ParIter<'a, T> { - entries: &'a [Bucket], + entries: &'a EntryVec>, } impl<'a, T> Clone for ParIter<'a, T> { @@ -450,6 +451,8 @@ where T: Ord, { self.with_entries(|entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.par_sort_by(|a, b| T::cmp(&a.key, &b.key)); }); } @@ -460,6 +463,8 @@ where F: Fn(&T, &T) -> Ordering + Sync, { self.with_entries(|entries| { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); entries.par_sort_by(move |a, b| cmp(&a.key, &b.key)); }); } @@ -471,7 +476,11 @@ where F: Fn(&T, &T) -> Ordering + Sync, { let mut entries = self.into_entries(); - entries.par_sort_by(move |a, b| cmp(&a.key, &b.key)); + { + #[cfg(feature = "amortize")] + let entries = entries.make_contiguous(); + entries.par_sort_by(move |a, b| cmp(&a.key, &b.key)); + } IntoParIter { entries } } } diff --git a/src/set.rs b/src/set.rs index fdd9e67d..41000da1 100644 --- a/src/set.rs +++ b/src/set.rs @@ -6,13 +6,12 @@ pub use crate::rayon::set as rayon; #[cfg(has_std)] use std::collections::hash_map::RandomState; -use crate::vec::{self, Vec}; +use crate::EntryVec; use core::cmp::Ordering; use core::fmt; use core::hash::{BuildHasher, Hash}; use core::iter::{Chain, FromIterator}; use core::ops::{BitAnd, BitOr, BitXor, RangeFull, Sub}; -use core::slice; use super::{Entries, Equivalent, IndexMap}; @@ -88,23 +87,23 @@ impl Entries for IndexSet { type Entry = Bucket; #[inline] - fn into_entries(self) -> Vec { + fn into_entries(self) -> EntryVec { self.map.into_entries() } #[inline] - fn as_entries(&self) -> &[Self::Entry] { + fn as_entries(&self) -> &EntryVec { self.map.as_entries() } #[inline] - fn as_entries_mut(&mut self) -> &mut [Self::Entry] { + fn as_entries_mut(&mut self) -> &mut EntryVec { self.map.as_entries_mut() } fn with_entries(&mut self, f: F) where - F: FnOnce(&mut [Self::Entry]), + F: FnOnce(&mut EntryVec), { self.map.with_entries(f); } @@ -610,7 +609,7 @@ impl IndexSet { /// [`IndexSet`]: struct.IndexSet.html /// [`into_iter`]: struct.IndexSet.html#method.into_iter pub struct IntoIter { - iter: vec::IntoIter>, + iter: > as IntoIterator>::IntoIter, } impl Iterator for IntoIter { @@ -632,6 +631,11 @@ impl ExactSizeIterator for IntoIter { } impl fmt::Debug for IntoIter { + #[cfg(feature = "amortize")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntoIter").finish() + } + #[cfg(not(feature = "amortize"))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let iter = self.iter.as_slice().iter().map(Bucket::key_ref); f.debug_list().entries(iter).finish() @@ -646,7 +650,7 @@ impl fmt::Debug for IntoIter { /// [`IndexSet`]: struct.IndexSet.html /// [`iter`]: struct.IndexSet.html#method.iter pub struct Iter<'a, T> { - iter: slice::Iter<'a, Bucket>, + iter: <&'a EntryVec> as IntoIterator>::IntoIter, } impl<'a, T> Iterator for Iter<'a, T> { @@ -689,7 +693,10 @@ impl<'a, T: fmt::Debug> fmt::Debug for Iter<'a, T> { /// [`IndexSet`]: struct.IndexSet.html /// [`drain`]: struct.IndexSet.html#method.drain pub struct Drain<'a, T> { - iter: vec::Drain<'a, Bucket>, + #[cfg(not(feature = "amortize"))] + iter: crate::vec::Drain<'a, Bucket>, + #[cfg(feature = "amortize")] + iter: atone::vc::Drain<'a, Bucket>, } impl<'a, T> Iterator for Drain<'a, T> { @@ -1349,7 +1356,9 @@ mod tests { assert_eq!(set.get(&i), Some(&i)); set.shrink_to_fit(); assert_eq!(set.len(), i + 1); - assert_eq!(set.capacity(), i + 1); + if !cfg!(feature = "amortize") { + assert_eq!(set.capacity(), i + 1); + } assert_eq!(set.get(&i), Some(&i)); } }