Skip to content

Commit 4f905bd

Browse files
committed
Optimize Clone with specialization
1 parent e189d70 commit 4f905bd

File tree

3 files changed

+113
-4
lines changed

3 files changed

+113
-4
lines changed

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: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,34 @@ impl<K: Clone, V: Clone, S: Clone> Clone for HashMap<K, V, S> {
206206
// want the table to have elements hashed with the wrong hash_builder.
207207
let hash_builder = source.hash_builder.clone();
208208

209-
self.table.clone_from(&source.table);
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.
210237
self.hash_builder = hash_builder;
211238
}
212239
}

src/raw/mod.rs

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

990-
new_table.clone_from_impl(self, |new_table| {
990+
new_table.clone_from_spec(self, |new_table| {
991991
// We need to free the memory allocated for the new table.
992992
new_table.free_buckets();
993993
});
@@ -1013,14 +1013,16 @@ impl<T: Clone> Clone for RawTable<T> {
10131013
// If necessary, resize our table to match the source.
10141014
if self.buckets() != source.buckets() {
10151015
// Skip our drop by using ptr::write.
1016-
self.free_buckets();
1016+
if !self.is_empty_singleton() {
1017+
self.free_buckets();
1018+
}
10171019
(self as *mut Self).write(
10181020
Self::new_uninitialized(source.buckets(), Fallibility::Infallible)
10191021
.unwrap_or_else(|_| hint::unreachable_unchecked()),
10201022
);
10211023
}
10221024

1023-
self.clone_from_impl(source, |self_| {
1025+
self.clone_from_spec(source, |self_| {
10241026
// We need to leave the table in an empty state.
10251027
self_.clear_no_drop()
10261028
});
@@ -1029,6 +1031,40 @@ impl<T: Clone> Clone for RawTable<T> {
10291031
}
10301032
}
10311033

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+
10321068
impl<T: Clone> RawTable<T> {
10331069
/// Common code for clone and clone_from. Assumes `self.buckets() == source.buckets()`.
10341070
#[cfg_attr(feature = "inline-more", inline)]
@@ -1071,6 +1107,51 @@ impl<T: Clone> RawTable<T> {
10711107
self.items = source.items;
10721108
self.growth_left = source.growth_left;
10731109
}
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);
1153+
}
1154+
}
10741155
}
10751156

10761157
#[cfg(feature = "nightly")]

0 commit comments

Comments
 (0)