diff --git a/benches/bench.rs b/benches/bench.rs index 4abdb17fb1..771e7169a5 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -206,3 +206,55 @@ bench_suite!( iter_ahash_random, iter_std_random ); + +#[bench] +fn clone_small(b: &mut Bencher) { + let mut m = HashMap::new(); + for i in 0..10 { + m.insert(i, 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, 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, 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, i); + } + + b.iter(|| { + m2.clone_from(&m); + black_box(&mut m2); + }) +} diff --git a/src/lib.rs b/src/lib.rs index 7662f03092..63b180c1ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ test, core_intrinsics, dropck_eyepatch, + specialization, ) )] #![allow( diff --git a/src/map.rs b/src/map.rs index 01946e2ba7..edb9401507 100644 --- a/src/map.rs +++ b/src/map.rs @@ -188,12 +188,56 @@ pub enum DefaultHashBuilder {} /// .iter().cloned().collect(); /// // use the values stored in map /// ``` -#[derive(Clone)] pub struct HashMap<K, V, S = DefaultHashBuilder> { pub(crate) hash_builder: S, pub(crate) table: RawTable<(K, V)>, } +impl<K: Clone, V: Clone, S: Clone> Clone for HashMap<K, V, S> { + fn clone(&self) -> Self { + HashMap { + hash_builder: self.hash_builder.clone(), + table: self.table.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + // We clone the hash_builder first since this might panic and we don't + // want the table to have elements hashed with the wrong hash_builder. + let hash_builder = source.hash_builder.clone(); + + #[cfg(not(feature = "nightly"))] + { + self.table.clone_from(&source.table); + } + #[cfg(feature = "nightly")] + { + trait HashClone<S> { + fn clone_from(&mut self, source: &Self, hash_builder: &S); + } + impl<K: Clone, V: Clone, S> HashClone<S> for HashMap<K, V, S> { + default fn clone_from(&mut self, source: &Self, _hash_builder: &S) { + self.table.clone_from(&source.table); + } + } + impl<K: Clone, V: Clone, S> HashClone<S> for HashMap<K, V, S> + where + K: Eq + Hash, + S: BuildHasher, + { + fn clone_from(&mut self, source: &Self, hash_builder: &S) { + self.table + .clone_from_with_hasher(&source.table, |x| make_hash(hash_builder, &x.0)); + } + } + HashClone::clone_from(self, source, &hash_builder); + } + + // Update hash_builder only if we successfully cloned all elements. + self.hash_builder = hash_builder; + } +} + #[cfg_attr(feature = "inline-more", inline)] pub(crate) fn make_hash<K: Hash + ?Sized>(hash_builder: &impl BuildHasher, val: &K) -> u64 { let mut state = hash_builder.build_hasher(); @@ -2820,6 +2864,21 @@ mod test_map { assert_eq!(m2.len(), 2); } + #[test] + fn test_clone_from() { + let mut m = HashMap::new(); + let mut m2 = HashMap::new(); + assert_eq!(m.len(), 0); + assert!(m.insert(1, 2).is_none()); + assert_eq!(m.len(), 1); + assert!(m.insert(2, 4).is_none()); + assert_eq!(m.len(), 2); + m2.clone_from(&m); + assert_eq!(*m2.get(&1).unwrap(), 2); + assert_eq!(*m2.get(&2).unwrap(), 4); + assert_eq!(m2.len(), 2); + } + thread_local! { static DROP_VECTOR: RefCell<Vec<i32>> = RefCell::new(Vec::new()) } #[derive(Hash, PartialEq, Eq)] diff --git a/src/raw/mod.rs b/src/raw/mod.rs index 8bc9641a2d..f22a63d4e2 100644 --- a/src/raw/mod.rs +++ b/src/raw/mod.rs @@ -987,43 +987,169 @@ impl<T: Clone> Clone for RawTable<T> { .unwrap_or_else(|_| hint::unreachable_unchecked()), ); - // Copy the control bytes unchanged. We do this in a single pass - self.ctrl(0) - .copy_to_nonoverlapping(new_table.ctrl(0), self.num_ctrl_bytes()); - - { - // The cloning of elements may panic, in which case we need - // to make sure we drop only the elements that have been - // cloned so far. - let mut guard = guard((0, &mut new_table), |(index, new_table)| { - if mem::needs_drop::<T>() { - for i in 0..=*index { - if is_full(*new_table.ctrl(i)) { - new_table.bucket(i).drop(); - } - } - } - new_table.free_buckets(); - }); + new_table.clone_from_spec(self, |new_table| { + // We need to free the memory allocated for the new table. + new_table.free_buckets(); + }); - for from in self.iter() { - let index = self.bucket_index(&from); - let to = guard.1.bucket(index); - to.write(from.as_ref().clone()); + // Return the newly created table. + ManuallyDrop::into_inner(new_table) + } + } + } - // Update the index in case we need to unwind. - guard.0 = index; + fn clone_from(&mut self, source: &Self) { + if source.is_empty_singleton() { + *self = Self::new(); + } else { + unsafe { + // First, drop all our elements without clearing the control bytes. + if mem::needs_drop::<T>() { + for item in self.iter() { + item.drop(); } + } - // Successfully cloned all items, no need to clean up. - mem::forget(guard); + // If necessary, resize our table to match the source. + if self.buckets() != source.buckets() { + // Skip our drop by using ptr::write. + if !self.is_empty_singleton() { + self.free_buckets(); + } + (self as *mut Self).write( + Self::new_uninitialized(source.buckets(), Fallibility::Infallible) + .unwrap_or_else(|_| hint::unreachable_unchecked()), + ); } - // Return the newly created table. - new_table.items = self.items; - new_table.growth_left = self.growth_left; - ManuallyDrop::into_inner(new_table) + self.clone_from_spec(source, |self_| { + // We need to leave the table in an empty state. + self_.clear_no_drop() + }); + } + } + } +} + +/// Specialization of `clone_from` for `Copy` types +trait RawTableClone { + unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)); +} +impl<T: Clone> RawTableClone for RawTable<T> { + #[cfg(feature = "nightly")] + #[cfg_attr(feature = "inline-more", inline)] + default unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) { + self.clone_from_impl(source, on_panic); + } + + #[cfg(not(feature = "nightly"))] + #[cfg_attr(feature = "inline-more", inline)] + unsafe fn clone_from_spec(&mut self, source: &Self, on_panic: impl FnMut(&mut Self)) { + self.clone_from_impl(source, on_panic); + } +} +#[cfg(feature = "nightly")] +impl<T: Copy> RawTableClone for RawTable<T> { + #[cfg_attr(feature = "inline-more", inline)] + unsafe fn clone_from_spec(&mut self, source: &Self, _on_panic: impl FnMut(&mut Self)) { + source + .ctrl(0) + .copy_to_nonoverlapping(self.ctrl(0), self.num_ctrl_bytes()); + source + .data + .as_ptr() + .copy_to_nonoverlapping(self.data.as_ptr(), self.buckets()); + + self.items = source.items; + self.growth_left = source.growth_left; + } +} + +impl<T: Clone> RawTable<T> { + /// Common code for clone and clone_from. Assumes `self.buckets() == source.buckets()`. + #[cfg_attr(feature = "inline-more", inline)] + unsafe fn clone_from_impl(&mut self, source: &Self, mut on_panic: impl FnMut(&mut Self)) { + // Copy the control bytes unchanged. We do this in a single pass + source + .ctrl(0) + .copy_to_nonoverlapping(self.ctrl(0), self.num_ctrl_bytes()); + + // The cloning of elements may panic, in which case we need + // to make sure we drop only the elements that have been + // cloned so far. + let mut guard = guard((0, &mut *self), |(index, self_)| { + if mem::needs_drop::<T>() { + for i in 0..=*index { + if is_full(*self_.ctrl(i)) { + self_.bucket(i).drop(); + } + } } + + // Depending on whether we were called from clone or clone_from, we + // either need to free the memory for the destination table or just + // clear the control bytes. + on_panic(self_); + }); + + for from in source.iter() { + let index = source.bucket_index(&from); + let to = guard.1.bucket(index); + to.write(from.as_ref().clone()); + + // Update the index in case we need to unwind. + guard.0 = index; + } + + // Successfully cloned all items, no need to clean up. + mem::forget(guard); + + self.items = source.items; + self.growth_left = source.growth_left; + } + + /// Variant of `clone_from` to use when a hasher is available. + #[cfg(any(feature = "nightly", feature = "raw"))] + pub fn clone_from_with_hasher(&mut self, source: &Self, hasher: impl Fn(&T) -> u64) { + // If we have enough capacity in the table, just clear it and insert + // elements one by one. We don't do this if we have the same number of + // buckets as the source since we can just copy the contents directly + // in that case. + if self.buckets() != source.buckets() + && bucket_mask_to_capacity(self.bucket_mask) >= source.len() + { + self.clear(); + + let guard_self = guard(&mut *self, |self_| { + // Clear the partially copied table if a panic occurs, otherwise + // items and growth_left will be out of sync with the contents + // of the table. + self_.clear(); + }); + + unsafe { + for item in source.iter() { + // This may panic. + let item = item.as_ref().clone(); + let hash = hasher(&item); + + // We can use a simpler version of insert() here since: + // - there are no DELETED entries. + // - we know there is enough space in the table. + // - all elements are unique. + let index = guard_self.find_insert_slot(hash); + guard_self.set_ctrl(index, h2(hash)); + guard_self.bucket(index).write(item); + } + } + + // Successfully cloned all items, no need to clean up. + mem::forget(guard_self); + + self.items = source.items; + self.growth_left -= source.items; + } else { + self.clone_from(source); } } } diff --git a/src/set.rs b/src/set.rs index 537a10d770..e5a94c2de5 100644 --- a/src/set.rs +++ b/src/set.rs @@ -111,11 +111,22 @@ use super::map::{self, DefaultHashBuilder, HashMap, Keys}; /// [`HashMap`]: struct.HashMap.html /// [`PartialEq`]: https://doc.rust-lang.org/std/cmp/trait.PartialEq.html /// [`RefCell`]: https://doc.rust-lang.org/std/cell/struct.RefCell.html -#[derive(Clone)] pub struct HashSet<T, S = DefaultHashBuilder> { pub(crate) map: HashMap<T, (), S>, } +impl<T: Clone, S: Clone> Clone for HashSet<T, S> { + fn clone(&self) -> Self { + HashSet { + map: self.map.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.map.clone_from(&source.map); + } +} + #[cfg(feature = "ahash")] impl<T: Hash + Eq> HashSet<T, DefaultHashBuilder> { /// Creates an empty `HashSet`.