Skip to content

Commit 02f78d6

Browse files
committed
Add support for amortized resizes
1 parent 041ee54 commit 02f78d6

File tree

9 files changed

+143
-55
lines changed

9 files changed

+143
-55
lines changed

Cargo.toml

+12-1
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,20 @@ bench = false
3434
autocfg = "1"
3535
[dependencies]
3636
serde = { version = "1.0", optional = true, default-features = false }
37-
rayon = { version = "1.0", optional = true }
37+
rayon_ = { version = "1.0", optional = true, package = "rayon" }
38+
atone = { version = "0.3.1", optional = true }
3839

3940
[dependencies.hashbrown]
4041
version = "0.8.1"
4142
default-features = false
4243
features = ["raw"]
4344

45+
[dependencies.griddle]
46+
version = "0.3.1"
47+
default-features = false
48+
features = ["raw"]
49+
optional = true
50+
4451
[dev-dependencies]
4552
itertools = "0.9"
4653
rand = {version = "0.7", features = ["small_rng"] }
@@ -52,6 +59,10 @@ fxhash = "0.2.1"
5259
[features]
5360
# Serialization with serde 1.0
5461
serde-1 = ["serde"]
62+
rayon = ["rayon_", "atone/rayon"]
63+
64+
# Use griddle over hashbrown, and atone over Vec, for amortized resizes
65+
amortize = ["griddle", "atone"]
5566

5667
# for testing only, of course
5768
test_low_transition_point = []

src/lib.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,11 @@ extern crate alloc;
8686
extern crate std;
8787

8888
#[cfg(not(has_std))]
89+
#[cfg_attr(feature = "amortize", allow(unused_imports))]
8990
use alloc::vec::{self, Vec};
9091

9192
#[cfg(has_std)]
93+
#[cfg_attr(feature = "amortize", allow(unused_imports))]
9294
use std::vec::{self, Vec};
9395

9496
#[macro_use]
@@ -113,6 +115,11 @@ pub use crate::set::IndexSet;
113115

114116
// shared private items
115117

118+
#[cfg(feature = "amortize")]
119+
type EntryVec<T> = atone::Vc<T>;
120+
#[cfg(not(feature = "amortize"))]
121+
type EntryVec<T> = Vec<T>;
122+
116123
/// Hash value newtype. Not larger than usize, since anything larger
117124
/// isn't used for selecting position anyway.
118125
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -182,10 +189,10 @@ impl<K, V> Bucket<K, V> {
182189

183190
trait Entries {
184191
type Entry;
185-
fn into_entries(self) -> Vec<Self::Entry>;
186-
fn as_entries(&self) -> &[Self::Entry];
187-
fn as_entries_mut(&mut self) -> &mut [Self::Entry];
192+
fn into_entries(self) -> EntryVec<Self::Entry>;
193+
fn as_entries(&self) -> &EntryVec<Self::Entry>;
194+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry>;
188195
fn with_entries<F>(&mut self, f: F)
189196
where
190-
F: FnOnce(&mut [Self::Entry]);
197+
F: FnOnce(&mut EntryVec<Self::Entry>);
191198
}

src/map.rs

+32-15
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ pub use crate::mutable_keys::MutableKeys;
88
#[cfg(feature = "rayon")]
99
pub use crate::rayon::map as rayon;
1010

11-
use crate::vec::{self, Vec};
11+
use crate::EntryVec;
1212
use ::core::cmp::Ordering;
1313
use ::core::fmt;
1414
use ::core::hash::{BuildHasher, Hash, Hasher};
1515
use ::core::iter::FromIterator;
1616
use ::core::ops::{Index, IndexMut, RangeFull};
17-
use ::core::slice::{Iter as SliceIter, IterMut as SliceIterMut};
1817

1918
#[cfg(has_std)]
2019
use std::collections::hash_map::RandomState;
@@ -101,23 +100,23 @@ impl<K, V, S> Entries for IndexMap<K, V, S> {
101100
type Entry = Bucket<K, V>;
102101

103102
#[inline]
104-
fn into_entries(self) -> Vec<Self::Entry> {
103+
fn into_entries(self) -> EntryVec<Self::Entry> {
105104
self.core.into_entries()
106105
}
107106

108107
#[inline]
109-
fn as_entries(&self) -> &[Self::Entry] {
108+
fn as_entries(&self) -> &EntryVec<Self::Entry> {
110109
self.core.as_entries()
111110
}
112111

113112
#[inline]
114-
fn as_entries_mut(&mut self) -> &mut [Self::Entry] {
113+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry> {
115114
self.core.as_entries_mut()
116115
}
117116

118117
fn with_entries<F>(&mut self, f: F)
119118
where
120-
F: FnOnce(&mut [Self::Entry]),
119+
F: FnOnce(&mut EntryVec<Self::Entry>),
121120
{
122121
self.core.with_entries(f);
123122
}
@@ -618,6 +617,8 @@ where
618617
K: Ord,
619618
{
620619
self.with_entries(|entries| {
620+
#[cfg(feature = "amortize")]
621+
let entries = entries.make_contiguous();
621622
entries.sort_by(|a, b| Ord::cmp(&a.key, &b.key));
622623
});
623624
}
@@ -635,6 +636,8 @@ where
635636
F: FnMut(&K, &V, &K, &V) -> Ordering,
636637
{
637638
self.with_entries(move |entries| {
639+
#[cfg(feature = "amortize")]
640+
let entries = entries.make_contiguous();
638641
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
639642
});
640643
}
@@ -648,7 +651,11 @@ where
648651
F: FnMut(&K, &V, &K, &V) -> Ordering,
649652
{
650653
let mut entries = self.into_entries();
651-
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
654+
{
655+
#[cfg(feature = "amortize")]
656+
let entries = entries.make_contiguous();
657+
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
658+
}
652659
IntoIter {
653660
iter: entries.into_iter(),
654661
}
@@ -724,7 +731,7 @@ impl<K, V, S> IndexMap<K, V, S> {
724731
/// [`keys`]: struct.IndexMap.html#method.keys
725732
/// [`IndexMap`]: struct.IndexMap.html
726733
pub struct Keys<'a, K, V> {
727-
pub(crate) iter: SliceIter<'a, Bucket<K, V>>,
734+
pub(crate) iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
728735
}
729736

730737
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> {
768775
/// [`values`]: struct.IndexMap.html#method.values
769776
/// [`IndexMap`]: struct.IndexMap.html
770777
pub struct Values<'a, K, V> {
771-
iter: SliceIter<'a, Bucket<K, V>>,
778+
iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
772779
}
773780

774781
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> {
812819
/// [`values_mut`]: struct.IndexMap.html#method.values_mut
813820
/// [`IndexMap`]: struct.IndexMap.html
814821
pub struct ValuesMut<'a, K, V> {
815-
iter: SliceIterMut<'a, Bucket<K, V>>,
822+
iter: <&'a mut EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
816823
}
817824

818825
impl<'a, K, V> Iterator for ValuesMut<'a, K, V> {
@@ -841,7 +848,7 @@ impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> {
841848
/// [`iter`]: struct.IndexMap.html#method.iter
842849
/// [`IndexMap`]: struct.IndexMap.html
843850
pub struct Iter<'a, K, V> {
844-
iter: SliceIter<'a, Bucket<K, V>>,
851+
iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
845852
}
846853

847854
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> {
885892
/// [`iter_mut`]: struct.IndexMap.html#method.iter_mut
886893
/// [`IndexMap`]: struct.IndexMap.html
887894
pub struct IterMut<'a, K, V> {
888-
iter: SliceIterMut<'a, Bucket<K, V>>,
895+
iter: <&'a mut EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
889896
}
890897

891898
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
@@ -914,7 +921,7 @@ impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> {
914921
/// [`into_iter`]: struct.IndexMap.html#method.into_iter
915922
/// [`IndexMap`]: struct.IndexMap.html
916923
pub struct IntoIter<K, V> {
917-
pub(crate) iter: vec::IntoIter<Bucket<K, V>>,
924+
pub(crate) iter: <EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
918925
}
919926

920927
impl<K, V> Iterator for IntoIter<K, V> {
@@ -936,6 +943,11 @@ impl<K, V> ExactSizeIterator for IntoIter<K, V> {
936943
}
937944

938945
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for IntoIter<K, V> {
946+
#[cfg(feature = "amortize")]
947+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948+
f.debug_struct("IntoIter").finish()
949+
}
950+
#[cfg(not(feature = "amortize"))]
939951
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
940952
let iter = self.iter.as_slice().iter().map(Bucket::refs);
941953
f.debug_list().entries(iter).finish()
@@ -950,7 +962,10 @@ impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for IntoIter<K, V> {
950962
/// [`drain`]: struct.IndexMap.html#method.drain
951963
/// [`IndexMap`]: struct.IndexMap.html
952964
pub struct Drain<'a, K, V> {
953-
pub(crate) iter: vec::Drain<'a, Bucket<K, V>>,
965+
#[cfg(not(feature = "amortize"))]
966+
pub(crate) iter: crate::vec::Drain<'a, Bucket<K, V>>,
967+
#[cfg(feature = "amortize")]
968+
pub(crate) iter: atone::vc::Drain<'a, Bucket<K, V>>,
954969
}
955970

956971
impl<'a, K, V> Iterator for Drain<'a, K, V> {
@@ -1307,7 +1322,9 @@ mod tests {
13071322
assert_eq!(map.get(&i), Some(&(i * i)));
13081323
map.shrink_to_fit();
13091324
assert_eq!(map.len(), i + 1);
1310-
assert_eq!(map.capacity(), i + 1);
1325+
if !cfg!(feature = "amortize") {
1326+
assert_eq!(map.capacity(), i + 1);
1327+
}
13111328
assert_eq!(map.get(&i), Some(&(i * i)));
13121329
}
13131330
}

src/map/core.rs

+27-10
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,35 @@
99
1010
mod raw;
1111

12+
#[cfg(feature = "amortize")]
13+
use griddle::raw::RawTable;
14+
#[cfg(not(feature = "amortize"))]
1215
use hashbrown::raw::RawTable;
1316

14-
use crate::vec::{Drain, Vec};
1517
use core::cmp;
1618
use core::fmt;
1719
use core::mem::replace;
1820
use core::ops::RangeFull;
1921

2022
use crate::equivalent::Equivalent;
2123
use crate::util::enumerate;
24+
use crate::EntryVec;
2225
use crate::{Bucket, Entries, HashValue};
26+
#[cfg(feature = "amortize")]
27+
use atone::vc::Drain;
28+
#[cfg(not(feature = "amortize"))]
29+
use std::vec::Drain;
2330

2431
/// Core of the map that does not depend on S
2532
pub(crate) struct IndexMapCore<K, V> {
2633
/// indices mapping from the entry hash to its index.
2734
indices: RawTable<usize>,
2835
/// entries is a dense vec of entries in their order.
29-
entries: Vec<Bucket<K, V>>,
36+
entries: EntryVec<Bucket<K, V>>,
3037
}
3138

3239
#[inline(always)]
33-
fn get_hash<K, V>(entries: &[Bucket<K, V>]) -> impl Fn(&usize) -> u64 + '_ {
40+
fn get_hash<K, V>(entries: &EntryVec<Bucket<K, V>>) -> impl Fn(&usize) -> u64 + '_ {
3441
move |&i| entries[i].hash.get()
3542
}
3643

@@ -40,8 +47,14 @@ where
4047
V: Clone,
4148
{
4249
fn clone(&self) -> Self {
50+
#[cfg(feature = "amortize")]
51+
let indices = {
52+
let hasher = get_hash(&self.entries);
53+
self.indices.clone_with_hasher(hasher)
54+
};
55+
#[cfg(not(feature = "amortize"))]
4356
let indices = self.indices.clone();
44-
let mut entries = Vec::with_capacity(indices.capacity());
57+
let mut entries = EntryVec::with_capacity(indices.capacity());
4558
entries.clone_from(&self.entries);
4659
IndexMapCore { indices, entries }
4760
}
@@ -74,23 +87,23 @@ impl<K, V> Entries for IndexMapCore<K, V> {
7487
type Entry = Bucket<K, V>;
7588

7689
#[inline]
77-
fn into_entries(self) -> Vec<Self::Entry> {
90+
fn into_entries(self) -> EntryVec<Self::Entry> {
7891
self.entries
7992
}
8093

8194
#[inline]
82-
fn as_entries(&self) -> &[Self::Entry] {
95+
fn as_entries(&self) -> &EntryVec<Self::Entry> {
8396
&self.entries
8497
}
8598

8699
#[inline]
87-
fn as_entries_mut(&mut self) -> &mut [Self::Entry] {
100+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry> {
88101
&mut self.entries
89102
}
90103

91104
fn with_entries<F>(&mut self, f: F)
92105
where
93-
F: FnOnce(&mut [Self::Entry]),
106+
F: FnOnce(&mut EntryVec<Self::Entry>),
94107
{
95108
f(&mut self.entries);
96109
self.rebuild_hash_table();
@@ -102,15 +115,15 @@ impl<K, V> IndexMapCore<K, V> {
102115
pub(crate) fn new() -> Self {
103116
IndexMapCore {
104117
indices: RawTable::new(),
105-
entries: Vec::new(),
118+
entries: EntryVec::new(),
106119
}
107120
}
108121

109122
#[inline]
110123
pub(crate) fn with_capacity(n: usize) -> Self {
111124
IndexMapCore {
112125
indices: RawTable::with_capacity(n),
113-
entries: Vec::with_capacity(n),
126+
entries: EntryVec::with_capacity(n),
114127
}
115128
}
116129

@@ -218,7 +231,11 @@ impl<K, V> IndexMapCore<K, V> {
218231
debug_assert!(self.indices.capacity() >= self.entries.len());
219232
for (i, entry) in enumerate(&self.entries) {
220233
// We should never have to reallocate, so there's no need for a real hasher.
234+
#[cfg(not(feature = "amortize"))]
221235
self.indices.insert_no_grow(entry.hash.get(), i);
236+
#[cfg(feature = "amortize")]
237+
self.indices
238+
.insert_no_grow(entry.hash.get(), i, |_| unreachable!());
222239
}
223240
}
224241
}

src/map/core/raw.rs

+9
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@
55
use super::{Entry, Equivalent, HashValue, IndexMapCore, VacantEntry};
66
use core::fmt;
77
use core::mem::replace;
8+
#[cfg(feature = "amortize")]
9+
use griddle::raw::RawTable;
10+
#[cfg(not(feature = "amortize"))]
811
use hashbrown::raw::RawTable;
912

13+
#[cfg(feature = "amortize")]
14+
type RawBucket = griddle::raw::Bucket<usize>;
15+
#[cfg(not(feature = "amortize"))]
1016
type RawBucket = hashbrown::raw::Bucket<usize>;
1117

1218
pub(super) struct DebugIndices<'a>(pub &'a RawTable<usize>);
@@ -105,6 +111,9 @@ impl<K, V> IndexMapCore<K, V> {
105111
// correct indices that point to the entries that followed the removed entry.
106112
// use a heuristic between a full sweep vs. a `find()` for every shifted item.
107113
let raw_capacity = self.indices.buckets();
114+
#[cfg(feature = "amortize")]
115+
let shifted_entries = self.entries.range(index..);
116+
#[cfg(not(feature = "amortize"))]
108117
let shifted_entries = &self.entries[index..];
109118
if shifted_entries.len() > raw_capacity / 2 {
110119
// shift all indices greater than `index`

0 commit comments

Comments
 (0)