Skip to content

Commit 4f4cdbc

Browse files
committed
Copy past len in Clone for simpler asm
1 parent 92fdfdf commit 4f4cdbc

1 file changed

Lines changed: 54 additions & 1 deletion

File tree

src/arrayvec.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1193,7 +1193,60 @@ impl<T, const CAP: usize> Clone for ArrayVec<T, CAP>
11931193
where T: Clone
11941194
{
11951195
fn clone(&self) -> Self {
1196-
self.iter().cloned().collect()
1196+
let mut array: ArrayVec<T, CAP> = ArrayVec::new();
1197+
{
1198+
let mut guard = ScopeExitGuard {
1199+
value: &mut array.len,
1200+
data: 0,
1201+
f: move |&len, self_len| {
1202+
**self_len = len as LenUint;
1203+
}
1204+
};
1205+
1206+
for i in 0..CAP {
1207+
if i < self.len() {
1208+
let val = unsafe { &*self.xs[i].as_ptr() }.clone();
1209+
if mem::size_of::<T>() != 0 {
1210+
unsafe { array.xs[i].as_mut_ptr().write(val) };
1211+
} else {
1212+
// The ZST element has logically been moved into the vector.
1213+
// There is no memory to write, but dropping `elt` here would
1214+
// drop it once now and once again when the vector is dropped.
1215+
mem::forget(val);
1216+
}
1217+
guard.data += 1;
1218+
} else {
1219+
// we are done copying all the elements.
1220+
// we continue to copy uninitialized elements past len up to CAP. This seems
1221+
// weird and counter intuitive, but when T is trivial (like i8/i32), copying the
1222+
// whole array is faster as the compiler doesnt have to loop up to len, and the
1223+
// generated code is branchless copy of the whole struct (like Copy).
1224+
//
1225+
// We only do it if;
1226+
// - size_of > 0, to avoid reading self.xs[i].as_ptr()
1227+
// - T does not requires drop, which we take as an indication that T::clone()
1228+
// is not trivial and therefore the loop will not be optimized away by the
1229+
// compiler.
1230+
// - the total array is less or equal to 128 bytes. An arbitrary threshold to
1231+
// avoid unnecessary copying of large arrays.
1232+
if mem::size_of::<T>() == 0 || std::mem::needs_drop::<T>() || CAP > 128 / mem::size_of::<T>() {
1233+
break;
1234+
}
1235+
// Safety: copy of MaybeUninit to MaybeUninit
1236+
unsafe {
1237+
std::ptr::copy_nonoverlapping(self.xs[i].as_ptr(), array.xs[i].as_mut_ptr(), 1)
1238+
};
1239+
}
1240+
}
1241+
}
1242+
1243+
// This assignment seems redundant as guard.data is already equal to len and will set the
1244+
// array len on drop, but setting it here explicitly helps the compiler understand it
1245+
// can just copy the len instead of accumulating it in the guard. This is especially
1246+
// useful for T::clone() that can not panic.
1247+
array.len = self.len;
1248+
1249+
array
11971250
}
11981251

11991252
fn clone_from(&mut self, rhs: &Self) {

0 commit comments

Comments
 (0)