Skip to content

Commit 4f7ce3d

Browse files
committed
Add support for amortized resizes
1 parent d293267 commit 4f7ce3d

File tree

9 files changed

+144
-53
lines changed

9 files changed

+144
-53
lines changed

Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,20 @@ bench = false
3333
autocfg = "1"
3434
[dependencies]
3535
serde = { version = "1.0", optional = true, default-features = false }
36-
rayon = { version = "1.0", optional = true }
36+
rayon_ = { version = "1.0", optional = true, package = "rayon" }
37+
atone = { version = "0.3.1", optional = true }
3738

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

44+
[dependencies.griddle]
45+
version = "0.3.1"
46+
default-features = false
47+
features = ["raw"]
48+
optional = true
49+
4350
[dev-dependencies]
4451
itertools = "0.9"
4552
rand = {version = "0.7", features = ["small_rng"] }
@@ -50,7 +57,11 @@ fxhash = "0.2.1"
5057

5158
[features]
5259
# Serialization with serde 1.0
53-
serde-1 = ["serde"]
60+
serde-1 = ["serde", "atone/serde", "griddle/serde"]
61+
rayon = ["rayon_", "atone/rayon", "griddle/rayon"]
62+
63+
# Use griddle over hashbrown, and atone over Vec, for amortized resizes
64+
amortize = ["griddle", "atone"]
5465

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

src/lib.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ extern crate alloc;
8484

8585
extern crate hashbrown;
8686

87+
#[cfg(feature = "amortize")]
88+
extern crate atone;
89+
#[cfg(feature = "amortize")]
90+
extern crate griddle;
91+
8792
#[cfg(not(has_std))]
8893
pub(crate) mod std {
8994
pub use core::*;
@@ -121,6 +126,11 @@ pub use set::IndexSet;
121126

122127
// shared private items
123128

129+
#[cfg(feature = "amortize")]
130+
type EntryVec<T> = atone::Vc<T>;
131+
#[cfg(not(feature = "amortize"))]
132+
type EntryVec<T> = Vec<T>;
133+
124134
/// Hash value newtype. Not larger than usize, since anything larger
125135
/// isn't used for selecting position anyway.
126136
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -190,10 +200,10 @@ impl<K, V> Bucket<K, V> {
190200

191201
trait Entries {
192202
type Entry;
193-
fn into_entries(self) -> Vec<Self::Entry>;
194-
fn as_entries(&self) -> &[Self::Entry];
195-
fn as_entries_mut(&mut self) -> &mut [Self::Entry];
203+
fn into_entries(self) -> EntryVec<Self::Entry>;
204+
fn as_entries(&self) -> &EntryVec<Self::Entry>;
205+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry>;
196206
fn with_entries<F>(&mut self, f: F)
197207
where
198-
F: FnOnce(&mut [Self::Entry]);
208+
F: FnOnce(&mut EntryVec<Self::Entry>);
199209
}

src/map.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use std::fmt;
2626
use self::core::IndexMapCore;
2727
use equivalent::Equivalent;
2828
use util::third;
29+
use EntryVec;
2930
use {Bucket, Entries, HashValue};
3031

3132
pub use self::core::{Entry, OccupiedEntry, VacantEntry};
@@ -105,23 +106,23 @@ impl<K, V, S> Entries for IndexMap<K, V, S> {
105106
type Entry = Bucket<K, V>;
106107

107108
#[inline]
108-
fn into_entries(self) -> Vec<Self::Entry> {
109+
fn into_entries(self) -> EntryVec<Self::Entry> {
109110
self.core.into_entries()
110111
}
111112

112113
#[inline]
113-
fn as_entries(&self) -> &[Self::Entry] {
114+
fn as_entries(&self) -> &EntryVec<Self::Entry> {
114115
self.core.as_entries()
115116
}
116117

117118
#[inline]
118-
fn as_entries_mut(&mut self) -> &mut [Self::Entry] {
119+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry> {
119120
self.core.as_entries_mut()
120121
}
121122

122123
fn with_entries<F>(&mut self, f: F)
123124
where
124-
F: FnOnce(&mut [Self::Entry]),
125+
F: FnOnce(&mut EntryVec<Self::Entry>),
125126
{
126127
self.core.with_entries(f);
127128
}
@@ -622,6 +623,8 @@ where
622623
K: Ord,
623624
{
624625
self.with_entries(|entries| {
626+
#[cfg(feature = "amortize")]
627+
let entries = entries.make_contiguous();
625628
entries.sort_by(|a, b| Ord::cmp(&a.key, &b.key));
626629
});
627630
}
@@ -639,6 +642,8 @@ where
639642
F: FnMut(&K, &V, &K, &V) -> Ordering,
640643
{
641644
self.with_entries(move |entries| {
645+
#[cfg(feature = "amortize")]
646+
let entries = entries.make_contiguous();
642647
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
643648
});
644649
}
@@ -652,7 +657,11 @@ where
652657
F: FnMut(&K, &V, &K, &V) -> Ordering,
653658
{
654659
let mut entries = self.into_entries();
655-
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
660+
{
661+
#[cfg(feature = "amortize")]
662+
let entries = entries.make_contiguous();
663+
entries.sort_by(move |a, b| cmp(&a.key, &a.value, &b.key, &b.value));
664+
}
656665
IntoIter {
657666
iter: entries.into_iter(),
658667
}
@@ -720,10 +729,6 @@ impl<K, V, S> IndexMap<K, V, S> {
720729
}
721730
}
722731

723-
use std::slice::Iter as SliceIter;
724-
use std::slice::IterMut as SliceIterMut;
725-
use std::vec::IntoIter as VecIntoIter;
726-
727732
/// An iterator over the keys of a `IndexMap`.
728733
///
729734
/// This `struct` is created by the [`keys`] method on [`IndexMap`]. See its
@@ -732,7 +737,7 @@ use std::vec::IntoIter as VecIntoIter;
732737
/// [`keys`]: struct.IndexMap.html#method.keys
733738
/// [`IndexMap`]: struct.IndexMap.html
734739
pub struct Keys<'a, K: 'a, V: 'a> {
735-
pub(crate) iter: SliceIter<'a, Bucket<K, V>>,
740+
pub(crate) iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
736741
}
737742

738743
impl<'a, K, V> Iterator for Keys<'a, K, V> {
@@ -776,7 +781,7 @@ impl<'a, K: fmt::Debug, V> fmt::Debug for Keys<'a, K, V> {
776781
/// [`values`]: struct.IndexMap.html#method.values
777782
/// [`IndexMap`]: struct.IndexMap.html
778783
pub struct Values<'a, K: 'a, V: 'a> {
779-
iter: SliceIter<'a, Bucket<K, V>>,
784+
iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
780785
}
781786

782787
impl<'a, K, V> Iterator for Values<'a, K, V> {
@@ -820,7 +825,7 @@ impl<'a, K, V: fmt::Debug> fmt::Debug for Values<'a, K, V> {
820825
/// [`values_mut`]: struct.IndexMap.html#method.values_mut
821826
/// [`IndexMap`]: struct.IndexMap.html
822827
pub struct ValuesMut<'a, K: 'a, V: 'a> {
823-
iter: SliceIterMut<'a, Bucket<K, V>>,
828+
iter: <&'a mut EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
824829
}
825830

826831
impl<'a, K, V> Iterator for ValuesMut<'a, K, V> {
@@ -849,7 +854,7 @@ impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> {
849854
/// [`iter`]: struct.IndexMap.html#method.iter
850855
/// [`IndexMap`]: struct.IndexMap.html
851856
pub struct Iter<'a, K: 'a, V: 'a> {
852-
iter: SliceIter<'a, Bucket<K, V>>,
857+
iter: <&'a EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
853858
}
854859

855860
impl<'a, K, V> Iterator for Iter<'a, K, V> {
@@ -893,7 +898,7 @@ impl<'a, K: fmt::Debug, V: fmt::Debug> fmt::Debug for Iter<'a, K, V> {
893898
/// [`iter_mut`]: struct.IndexMap.html#method.iter_mut
894899
/// [`IndexMap`]: struct.IndexMap.html
895900
pub struct IterMut<'a, K: 'a, V: 'a> {
896-
iter: SliceIterMut<'a, Bucket<K, V>>,
901+
iter: <&'a mut EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
897902
}
898903

899904
impl<'a, K, V> Iterator for IterMut<'a, K, V> {
@@ -922,7 +927,7 @@ impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> {
922927
/// [`into_iter`]: struct.IndexMap.html#method.into_iter
923928
/// [`IndexMap`]: struct.IndexMap.html
924929
pub struct IntoIter<K, V> {
925-
pub(crate) iter: VecIntoIter<Bucket<K, V>>,
930+
pub(crate) iter: <EntryVec<Bucket<K, V>> as IntoIterator>::IntoIter,
926931
}
927932

928933
impl<K, V> Iterator for IntoIter<K, V> {
@@ -944,6 +949,11 @@ impl<K, V> ExactSizeIterator for IntoIter<K, V> {
944949
}
945950

946951
impl<K: fmt::Debug, V: fmt::Debug> fmt::Debug for IntoIter<K, V> {
952+
#[cfg(feature = "amortize")]
953+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
954+
f.debug_struct("IntoIter").finish()
955+
}
956+
#[cfg(not(feature = "amortize"))]
947957
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
948958
let iter = self.iter.as_slice().iter().map(Bucket::refs);
949959
f.debug_list().entries(iter).finish()
@@ -962,7 +972,10 @@ where
962972
K: 'a,
963973
V: 'a,
964974
{
975+
#[cfg(not(feature = "amortize"))]
965976
pub(crate) iter: ::std::vec::Drain<'a, Bucket<K, V>>,
977+
#[cfg(feature = "amortize")]
978+
pub(crate) iter: atone::vc::Drain<'a, Bucket<K, V>>,
966979
}
967980

968981
impl<'a, K, V> Iterator for Drain<'a, K, V> {
@@ -1320,7 +1333,9 @@ mod tests {
13201333
assert_eq!(map.get(&i), Some(&(i * i)));
13211334
map.shrink_to_fit();
13221335
assert_eq!(map.len(), i + 1);
1323-
assert_eq!(map.capacity(), i + 1);
1336+
if !cfg!(feature = "amortize") {
1337+
assert_eq!(map.capacity(), i + 1);
1338+
}
13241339
assert_eq!(map.get(&i), Some(&(i * i)));
13251340
}
13261341
}

src/map/core.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,36 @@ mod raw;
1212
#[cfg(not(has_std))]
1313
use std::vec::Vec;
1414

15+
#[cfg(feature = "amortize")]
16+
use griddle::raw::RawTable;
17+
#[cfg(not(feature = "amortize"))]
1518
use hashbrown::raw::RawTable;
1619

1720
use std::cmp;
1821
use std::fmt;
1922
use std::mem::replace;
2023
use std::ops::RangeFull;
24+
25+
#[cfg(feature = "amortize")]
26+
use atone::vc::Drain;
27+
#[cfg(not(feature = "amortize"))]
2128
use std::vec::Drain;
2229

2330
use equivalent::Equivalent;
2431
use util::enumerate;
32+
use EntryVec;
2533
use {Bucket, Entries, HashValue};
2634

2735
/// Core of the map that does not depend on S
2836
pub(crate) struct IndexMapCore<K, V> {
2937
/// indices mapping from the entry hash to its index.
3038
indices: RawTable<usize>,
3139
/// entries is a dense vec of entries in their order.
32-
entries: Vec<Bucket<K, V>>,
40+
entries: EntryVec<Bucket<K, V>>,
3341
}
3442

3543
#[inline(always)]
36-
fn get_hash<K, V>(entries: &[Bucket<K, V>]) -> impl Fn(&usize) -> u64 + '_ {
44+
fn get_hash<K, V>(entries: &EntryVec<Bucket<K, V>>) -> impl Fn(&usize) -> u64 + '_ {
3745
move |&i| entries[i].hash.get()
3846
}
3947

@@ -43,8 +51,14 @@ where
4351
V: Clone,
4452
{
4553
fn clone(&self) -> Self {
54+
#[cfg(feature = "amortize")]
55+
let indices = {
56+
let hasher = get_hash(&self.entries);
57+
self.indices.clone_with_hasher(hasher)
58+
};
59+
#[cfg(not(feature = "amortize"))]
4660
let indices = self.indices.clone();
47-
let mut entries = Vec::with_capacity(indices.capacity());
61+
let mut entries = EntryVec::with_capacity(indices.capacity());
4862
entries.clone_from(&self.entries);
4963
IndexMapCore { indices, entries }
5064
}
@@ -77,23 +91,23 @@ impl<K, V> Entries for IndexMapCore<K, V> {
7791
type Entry = Bucket<K, V>;
7892

7993
#[inline]
80-
fn into_entries(self) -> Vec<Self::Entry> {
94+
fn into_entries(self) -> EntryVec<Self::Entry> {
8195
self.entries
8296
}
8397

8498
#[inline]
85-
fn as_entries(&self) -> &[Self::Entry] {
99+
fn as_entries(&self) -> &EntryVec<Self::Entry> {
86100
&self.entries
87101
}
88102

89103
#[inline]
90-
fn as_entries_mut(&mut self) -> &mut [Self::Entry] {
104+
fn as_entries_mut(&mut self) -> &mut EntryVec<Self::Entry> {
91105
&mut self.entries
92106
}
93107

94108
fn with_entries<F>(&mut self, f: F)
95109
where
96-
F: FnOnce(&mut [Self::Entry]),
110+
F: FnOnce(&mut EntryVec<Self::Entry>),
97111
{
98112
f(&mut self.entries);
99113
self.rebuild_hash_table();
@@ -105,15 +119,15 @@ impl<K, V> IndexMapCore<K, V> {
105119
pub(crate) fn new() -> Self {
106120
IndexMapCore {
107121
indices: RawTable::new(),
108-
entries: Vec::new(),
122+
entries: EntryVec::new(),
109123
}
110124
}
111125

112126
#[inline]
113127
pub(crate) fn with_capacity(n: usize) -> Self {
114128
IndexMapCore {
115129
indices: RawTable::with_capacity(n),
116-
entries: Vec::with_capacity(n),
130+
entries: EntryVec::with_capacity(n),
117131
}
118132
}
119133

@@ -221,7 +235,11 @@ impl<K, V> IndexMapCore<K, V> {
221235
debug_assert!(self.indices.capacity() >= self.entries.len());
222236
for (i, entry) in enumerate(&self.entries) {
223237
// We should never have to reallocate, so there's no need for a real hasher.
238+
#[cfg(not(feature = "amortize"))]
224239
self.indices.insert_no_grow(entry.hash.get(), i);
240+
#[cfg(feature = "amortize")]
241+
self.indices
242+
.insert_no_grow(entry.hash.get(), i, |_| unreachable!());
225243
}
226244
}
227245
}

src/map/core/raw.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
//! mostly in dealing with its bucket "pointers".
44
55
use super::{Entry, Equivalent, HashValue, IndexMapCore, VacantEntry};
6-
use hashbrown::raw::RawTable;
76
use std::fmt;
87
use std::mem::replace;
98

9+
#[cfg(feature = "amortize")]
10+
use griddle::raw::RawTable;
11+
#[cfg(not(feature = "amortize"))]
12+
use hashbrown::raw::RawTable;
13+
14+
#[cfg(feature = "amortize")]
15+
type RawBucket = griddle::raw::Bucket<usize>;
16+
#[cfg(not(feature = "amortize"))]
1017
type RawBucket = hashbrown::raw::Bucket<usize>;
1118

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

0 commit comments

Comments
 (0)