Skip to content

Commit 108822c

Browse files
committed
Auto merge of #146 - Amanieu:clone_from, r=Amanieu
Optimize Clone implementation Two major changes: - `clone_from` is now implemented which can be used to skip memory allocation if the existing `HashMap` already has enough capacity. - [nightly] Specialization is used to optimize cloning when `T: Copy` into a simple memory copy. - [nightly] Specialization is used to reuse the existing capacity of a `HashMap` when `clone_from` is used. Specialization is needed because the `Clone` impl on `HashMap` is missing `K: Hash + Eq, S: BuildHasher`. Fixes #112
2 parents 6f8703d + c9b320e commit 108822c

File tree

5 files changed

+281
-32
lines changed

5 files changed

+281
-32
lines changed

benches/bench.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,55 @@ bench_suite!(
206206
iter_ahash_random,
207207
iter_std_random
208208
);
209+
210+
#[bench]
211+
fn clone_small(b: &mut Bencher) {
212+
let mut m = HashMap::new();
213+
for i in 0..10 {
214+
m.insert(i, i);
215+
}
216+
217+
b.iter(|| {
218+
black_box(m.clone());
219+
})
220+
}
221+
222+
#[bench]
223+
fn clone_from_small(b: &mut Bencher) {
224+
let mut m = HashMap::new();
225+
let mut m2 = HashMap::new();
226+
for i in 0..10 {
227+
m.insert(i, i);
228+
}
229+
230+
b.iter(|| {
231+
m2.clone_from(&m);
232+
black_box(&mut m2);
233+
})
234+
}
235+
236+
#[bench]
237+
fn clone_large(b: &mut Bencher) {
238+
let mut m = HashMap::new();
239+
for i in 0..1000 {
240+
m.insert(i, i);
241+
}
242+
243+
b.iter(|| {
244+
black_box(m.clone());
245+
})
246+
}
247+
248+
#[bench]
249+
fn clone_from_large(b: &mut Bencher) {
250+
let mut m = HashMap::new();
251+
let mut m2 = HashMap::new();
252+
for i in 0..1000 {
253+
m.insert(i, i);
254+
}
255+
256+
b.iter(|| {
257+
m2.clone_from(&m);
258+
black_box(&mut m2);
259+
})
260+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
test,
2020
core_intrinsics,
2121
dropck_eyepatch,
22+
specialization,
2223
)
2324
)]
2425
#![allow(

src/map.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,56 @@ pub enum DefaultHashBuilder {}
188188
/// .iter().cloned().collect();
189189
/// // use the values stored in map
190190
/// ```
191-
#[derive(Clone)]
192191
pub struct HashMap<K, V, S = DefaultHashBuilder> {
193192
pub(crate) hash_builder: S,
194193
pub(crate) table: RawTable<(K, V)>,
195194
}
196195

196+
impl<K: Clone, V: Clone, S: Clone> Clone for HashMap<K, V, S> {
197+
fn clone(&self) -> Self {
198+
HashMap {
199+
hash_builder: self.hash_builder.clone(),
200+
table: self.table.clone(),
201+
}
202+
}
203+
204+
fn clone_from(&mut self, source: &Self) {
205+
// We clone the hash_builder first since this might panic and we don't
206+
// want the table to have elements hashed with the wrong hash_builder.
207+
let hash_builder = source.hash_builder.clone();
208+
209+
#[cfg(not(feature = "nightly"))]
210+
{
211+
self.table.clone_from(&source.table);
212+
}
213+
#[cfg(feature = "nightly")]
214+
{
215+
trait HashClone<S> {
216+
fn clone_from(&mut self, source: &Self, hash_builder: &S);
217+
}
218+
impl<K: Clone, V: Clone, S> HashClone<S> for HashMap<K, V, S> {
219+
default fn clone_from(&mut self, source: &Self, _hash_builder: &S) {
220+
self.table.clone_from(&source.table);
221+
}
222+
}
223+
impl<K: Clone, V: Clone, S> HashClone<S> for HashMap<K, V, S>
224+
where
225+
K: Eq + Hash,
226+
S: BuildHasher,
227+
{
228+
fn clone_from(&mut self, source: &Self, hash_builder: &S) {
229+
self.table
230+
.clone_from_with_hasher(&source.table, |x| make_hash(hash_builder, &x.0));
231+
}
232+
}
233+
HashClone::clone_from(self, source, &hash_builder);
234+
}
235+
236+
// Update hash_builder only if we successfully cloned all elements.
237+
self.hash_builder = hash_builder;
238+
}
239+
}
240+
197241
#[cfg_attr(feature = "inline-more", inline)]
198242
pub(crate) fn make_hash<K: Hash + ?Sized>(hash_builder: &impl BuildHasher, val: &K) -> u64 {
199243
let mut state = hash_builder.build_hasher();
@@ -2820,6 +2864,21 @@ mod test_map {
28202864
assert_eq!(m2.len(), 2);
28212865
}
28222866

2867+
#[test]
2868+
fn test_clone_from() {
2869+
let mut m = HashMap::new();
2870+
let mut m2 = HashMap::new();
2871+
assert_eq!(m.len(), 0);
2872+
assert!(m.insert(1, 2).is_none());
2873+
assert_eq!(m.len(), 1);
2874+
assert!(m.insert(2, 4).is_none());
2875+
assert_eq!(m.len(), 2);
2876+
m2.clone_from(&m);
2877+
assert_eq!(*m2.get(&1).unwrap(), 2);
2878+
assert_eq!(*m2.get(&2).unwrap(), 4);
2879+
assert_eq!(m2.len(), 2);
2880+
}
2881+
28232882
thread_local! { static DROP_VECTOR: RefCell<Vec<i32>> = RefCell::new(Vec::new()) }
28242883

28252884
#[derive(Hash, PartialEq, Eq)]

src/raw/mod.rs

Lines changed: 156 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -987,43 +987,169 @@ impl<T: Clone> Clone for RawTable<T> {
987987
.unwrap_or_else(|_| hint::unreachable_unchecked()),
988988
);
989989

990-
// Copy the control bytes unchanged. We do this in a single pass
991-
self.ctrl(0)
992-
.copy_to_nonoverlapping(new_table.ctrl(0), self.num_ctrl_bytes());
993-
994-
{
995-
// The cloning of elements may panic, in which case we need
996-
// to make sure we drop only the elements that have been
997-
// cloned so far.
998-
let mut guard = guard((0, &mut new_table), |(index, new_table)| {
999-
if mem::needs_drop::<T>() {
1000-
for i in 0..=*index {
1001-
if is_full(*new_table.ctrl(i)) {
1002-
new_table.bucket(i).drop();
1003-
}
1004-
}
1005-
}
1006-
new_table.free_buckets();
1007-
});
990+
new_table.clone_from_spec(self, |new_table| {
991+
// We need to free the memory allocated for the new table.
992+
new_table.free_buckets();
993+
});
1008994

1009-
for from in self.iter() {
1010-
let index = self.bucket_index(&from);
1011-
let to = guard.1.bucket(index);
1012-
to.write(from.as_ref().clone());
995+
// Return the newly created table.
996+
ManuallyDrop::into_inner(new_table)
997+
}
998+
}
999+
}
10131000

1014-
// Update the index in case we need to unwind.
1015-
guard.0 = index;
1001+
fn clone_from(&mut self, source: &Self) {
1002+
if source.is_empty_singleton() {
1003+
*self = Self::new();
1004+
} else {
1005+
unsafe {
1006+
// First, drop all our elements without clearing the control bytes.
1007+
if mem::needs_drop::<T>() {
1008+
for item in self.iter() {
1009+
item.drop();
10161010
}
1011+
}
10171012

1018-
// Successfully cloned all items, no need to clean up.
1019-
mem::forget(guard);
1013+
// If necessary, resize our table to match the source.
1014+
if self.buckets() != source.buckets() {
1015+
// Skip our drop by using ptr::write.
1016+
if !self.is_empty_singleton() {
1017+
self.free_buckets();
1018+
}
1019+
(self as *mut Self).write(
1020+
Self::new_uninitialized(source.buckets(), Fallibility::Infallible)
1021+
.unwrap_or_else(|_| hint::unreachable_unchecked()),
1022+
);
10201023
}
10211024

1022-
// Return the newly created table.
1023-
new_table.items = self.items;
1024-
new_table.growth_left = self.growth_left;
1025-
ManuallyDrop::into_inner(new_table)
1025+
self.clone_from_spec(source, |self_| {
1026+
// We need to leave the table in an empty state.
1027+
self_.clear_no_drop()
1028+
});
1029+
}
1030+
}
1031+
}
1032+
}
1033+
1034+
/// Specialization of `clone_from` for `Copy` types
1035+
trait RawTableClone {
1036+
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self));
1037+
}
1038+
impl<T: Clone> RawTableClone for RawTable<T> {
1039+
#[cfg(feature = "nightly")]
1040+
#[cfg_attr(feature = "inline-more", inline)]
1041+
default unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) {
1042+
self.clone_from_impl(source, on_panic);
1043+
}
1044+
1045+
#[cfg(not(feature = "nightly"))]
1046+
#[cfg_attr(feature = "inline-more", inline)]
1047+
unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) {
1048+
self.clone_from_impl(source, on_panic);
1049+
}
1050+
}
1051+
#[cfg(feature = "nightly")]
1052+
impl<T: Copy> RawTableClone for RawTable<T> {
1053+
#[cfg_attr(feature = "inline-more", inline)]
1054+
unsafe fn clone_from_spec(&mut self, source: &Self, _on_panic: impl FnMut(&mut Self)) {
1055+
source
1056+
.ctrl(0)
1057+
.copy_to_nonoverlapping(self.ctrl(0), self.num_ctrl_bytes());
1058+
source
1059+
.data
1060+
.as_ptr()
1061+
.copy_to_nonoverlapping(self.data.as_ptr(), self.buckets());
1062+
1063+
self.items = source.items;
1064+
self.growth_left = source.growth_left;
1065+
}
1066+
}
1067+
1068+
impl<T: Clone> RawTable<T> {
1069+
/// Common code for clone and clone_from. Assumes `self.buckets() == source.buckets()`.
1070+
#[cfg_attr(feature = "inline-more", inline)]
1071+
unsafe fn clone_from_impl(&mut self, source: &Self, mut on_panic: impl FnMut(&mut Self)) {
1072+
// Copy the control bytes unchanged. We do this in a single pass
1073+
source
1074+
.ctrl(0)
1075+
.copy_to_nonoverlapping(self.ctrl(0), self.num_ctrl_bytes());
1076+
1077+
// The cloning of elements may panic, in which case we need
1078+
// to make sure we drop only the elements that have been
1079+
// cloned so far.
1080+
let mut guard = guard((0, &mut *self), |(index, self_)| {
1081+
if mem::needs_drop::<T>() {
1082+
for i in 0..=*index {
1083+
if is_full(*self_.ctrl(i)) {
1084+
self_.bucket(i).drop();
1085+
}
1086+
}
10261087
}
1088+
1089+
// Depending on whether we were called from clone or clone_from, we
1090+
// either need to free the memory for the destination table or just
1091+
// clear the control bytes.
1092+
on_panic(self_);
1093+
});
1094+
1095+
for from in source.iter() {
1096+
let index = source.bucket_index(&from);
1097+
let to = guard.1.bucket(index);
1098+
to.write(from.as_ref().clone());
1099+
1100+
// Update the index in case we need to unwind.
1101+
guard.0 = index;
1102+
}
1103+
1104+
// Successfully cloned all items, no need to clean up.
1105+
mem::forget(guard);
1106+
1107+
self.items = source.items;
1108+
self.growth_left = source.growth_left;
1109+
}
1110+
1111+
/// Variant of `clone_from` to use when a hasher is available.
1112+
#[cfg(any(feature = "nightly", feature = "raw"))]
1113+
pub fn clone_from_with_hasher(&mut self, source: &Self, hasher: impl Fn(&T) -> u64) {
1114+
// If we have enough capacity in the table, just clear it and insert
1115+
// elements one by one. We don't do this if we have the same number of
1116+
// buckets as the source since we can just copy the contents directly
1117+
// in that case.
1118+
if self.buckets() != source.buckets()
1119+
&& bucket_mask_to_capacity(self.bucket_mask) >= source.len()
1120+
{
1121+
self.clear();
1122+
1123+
let guard_self = guard(&mut *self, |self_| {
1124+
// Clear the partially copied table if a panic occurs, otherwise
1125+
// items and growth_left will be out of sync with the contents
1126+
// of the table.
1127+
self_.clear();
1128+
});
1129+
1130+
unsafe {
1131+
for item in source.iter() {
1132+
// This may panic.
1133+
let item = item.as_ref().clone();
1134+
let hash = hasher(&item);
1135+
1136+
// We can use a simpler version of insert() here since:
1137+
// - there are no DELETED entries.
1138+
// - we know there is enough space in the table.
1139+
// - all elements are unique.
1140+
let index = guard_self.find_insert_slot(hash);
1141+
guard_self.set_ctrl(index, h2(hash));
1142+
guard_self.bucket(index).write(item);
1143+
}
1144+
}
1145+
1146+
// Successfully cloned all items, no need to clean up.
1147+
mem::forget(guard_self);
1148+
1149+
self.items = source.items;
1150+
self.growth_left -= source.items;
1151+
} else {
1152+
self.clone_from(source);
10271153
}
10281154
}
10291155
}

src/set.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,22 @@ use super::map::{self, DefaultHashBuilder, HashMap, Keys};
111111
/// [`HashMap`]: struct.HashMap.html
112112
/// [`PartialEq`]: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html
113113
/// [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
114-
#[derive(Clone)]
115114
pub struct HashSet<T, S = DefaultHashBuilder> {
116115
pub(crate) map: HashMap<T, (), S>,
117116
}
118117

118+
impl<T: Clone, S: Clone> Clone for HashSet<T, S> {
119+
fn clone(&self) -> Self {
120+
HashSet {
121+
map: self.map.clone(),
122+
}
123+
}
124+
125+
fn clone_from(&mut self, source: &Self) {
126+
self.map.clone_from(&source.map);
127+
}
128+
}
129+
119130
#[cfg(feature = "ahash")]
120131
impl<T: Hash + Eq> HashSet<T, DefaultHashBuilder> {
121132
/// Creates an empty `HashSet`.

0 commit comments

Comments
 (0)