Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions library/alloc/src/collections/vec_deque/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,78 @@ pub struct VecDeque<
impl<T: Clone, A: Allocator + Clone> Clone for VecDeque<T, A> {
fn clone(&self) -> Self {
let mut deq = Self::with_capacity_in(self.len(), self.allocator().clone());
deq.extend(self.iter().cloned());
deq.spec_clone_from(self);
deq
}

/// Overwrites the contents of `self` with a clone of the contents of `source`.
///
/// This method is preferred over simply assigning `source.clone()` to `self`,
/// as it avoids reallocation if possible.
/// as it avoids reallocation if possible. Additionally, if the element type
/// `T` overrides `clone_from()`, this will reuse the resources of `self`'s
/// elements as well.
fn clone_from(&mut self, source: &Self) {
self.spec_clone_from(source);
}
}

// The trait is required to prevent internal details of the implementation leaking in rustdoc.
trait SpecCloneFrom {
fn spec_clone_from(&mut self, source: &Self);
}

impl<T: Clone, A: Allocator> SpecCloneFrom for VecDeque<T, A> {
default fn spec_clone_from(&mut self, source: &Self) {
self.truncate(source.len());

// We need to clone the overlapping elements in chunks given the deques may wrap at
// different points.
let (dst_front, dst_back) = self.as_mut_slices();
let (mut src_front, mut src_back) = source.as_slices();
for mut destination in [dst_front, dst_back] {
while !destination.is_empty() {
if src_front.is_empty() {
src_front = src_back;
src_back = &[];
}
let len = cmp::min(destination.len(), src_front.len());
debug_assert!(len > 0);
let (dst, dst_rest) = destination.split_at_mut(len);
let (src, src_rest) = src_front.split_at(len);
dst.clone_from_slice(src);
destination = dst_rest;
src_front = src_rest;
}
}

self.extend(src_front.iter().chain(src_back).cloned());
}
}

impl<T: TrivialClone, A: Allocator> SpecCloneFrom for VecDeque<T, A> {
fn spec_clone_from(&mut self, source: &Self) {
self.clear();
self.extend(source.iter().cloned());
self.reserve(source.len());

let (front, back) = source.as_slices();
// SAFETY:
// - `TrivialClone` allows cloning by copying the bits.
// - `clear` dropped all destination elements, leaving no live values to overwrite.
// - The source slices are initialized, and all pointers are properly aligned for `T`.
// - `reserve` ensures capacity for `front.len() + back.len() == source.len()`
// elements, so both destination ranges and `dst.add(front.len())` are in bounds.
// - For non-ZSTs, the deques own distinct allocations, so the copied ranges do not
// overlap. For ZSTs, the copies and pointer offset have size zero in bytes.
unsafe {
let dst = self.ptr();
ptr::copy_nonoverlapping(front.as_ptr(), dst, front.len());
if !back.is_empty() {
ptr::copy_nonoverlapping(back.as_ptr(), dst.add(front.len()), back.len());
}
}
// SAFETY: The copies initialized `source.len()` elements starting at index zero.
self.head = WrappedIndex::zero();
self.len = source.len();
}
}

Expand Down
135 changes: 135 additions & 0 deletions library/alloctests/benches/vec_deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,141 @@ fn bench_grow_1025(b: &mut Bencher) {
})
}

/// first_len is the length of the first slice returned by as_slices
fn clone_fixture<T: Clone>(value: &T, (len, first_len): (usize, usize)) -> VecDeque<T> {
let mut deque = VecDeque::with_capacity(len);
let push_front = if first_len == len { 0 } else { first_len };
deque.resize_with(len - push_front, || value.clone());
for _ in 0..push_front {
deque.push_front(value.clone());
}
let (first, second) = deque.as_slices();
assert_eq!((first.len(), second.len()), (first_len, len - first_len));
deque
}

/// times allocation and drop as well as cloning
fn do_bench_clone<T: Clone>(b: &mut Bencher, value: T, layout: (usize, usize)) {
let src = clone_fixture(&value, layout);

b.iter(|| black_box(black_box(&src).clone()));
}

fn do_bench_clone_batch_32<T: Clone>(b: &mut Bencher, value: T, layout: (usize, usize)) {
let src = clone_fixture(&value, layout);

b.iter(|| {
// keep all clones alive so the allocator can't reuse the same buffer for each clone
let clones: [VecDeque<T>; 32] = std::array::from_fn(|_| black_box(&src).clone());
black_box(&clones);
});
}

/// clone_from may make the destination contiguous even when the source is wrapped
fn do_bench_clone_from<T: Clone>(b: &mut Bencher, value: T, layout: (usize, usize)) {
let src = clone_fixture(&value, layout);
let mut dst = clone_fixture(&value, layout);

b.iter(|| {
dst.clone_from(black_box(&src));
black_box(&dst);
});
}

/// shrink and grow through clone_from so the timing doesn't include a separate reset
fn do_bench_clone_from_alternating<T: Clone>(
b: &mut Bencher,
value: T,
long: (usize, usize),
short: (usize, usize),
) {
let long_src = clone_fixture(&value, long);
let short_src = clone_fixture(&value, short);
let mut dst = clone_fixture(&value, long);

b.iter(|| {
dst.clone_from(black_box(&short_src));
dst.clone_from(black_box(&long_src));
black_box(&dst);
});
}

fn do_bench_clone_from_empty<T: Clone>(b: &mut Bencher, value: T, layout: (usize, usize)) {
let src = clone_fixture(&value, layout);

b.iter(|| {
let mut dst = VecDeque::new();
dst.clone_from(black_box(&src));
black_box(dst);
});
}

macro_rules! clone_benches {
($($name:ident, $value:expr, $layout:expr;)*) => {
$(
#[bench]
fn ${concat(bench_clone_, $name)}(b: &mut Bencher) {
do_bench_clone(b, $value, $layout);
}

#[bench]
fn ${concat(bench_clone_from_, $name)}(b: &mut Bencher) {
do_bench_clone_from(b, $value, $layout);
}
)*
};
}

clone_benches! {
u64_empty, 42u64, (0, 0);
u64_one, 42u64, (1, 1);
u64_small, 42u64, (16, 16);
u64_small_wrapped, 42u64, (16, 5);
u64_contiguous, 42u64, (1024, 1024);
u64_wrapped, 42u64, (1024, 384);
string_contiguous, "abcdefgh".repeat(15), (1024, 1024);
string_wrapped, "abcdefgh".repeat(15), (1024, 384);
zst, (), (1024, 1024);
}

#[bench]
fn bench_clone_u64_small_batch_32(b: &mut Bencher) {
do_bench_clone_batch_32(b, 42u64, (16, 16));
}

#[bench]
fn bench_clone_u64_small_wrapped_batch_32(b: &mut Bencher) {
do_bench_clone_batch_32(b, 42u64, (16, 5));
}

macro_rules! clone_from_benches {
($($name:ident, $value:expr, $long:expr, $short:expr;)*) => {
$(
#[bench]
fn ${concat(bench_clone_from_, $name)}(b: &mut Bencher) {
do_bench_clone_from_alternating(b, $value, $long, $short);
}
)*
};
}

clone_from_benches! {
u64_alternating_contiguous, 42u64, (1024, 1024), (512, 512);
u64_alternating_wrapped, 42u64, (1024, 384), (512, 192);
string_alternating_contiguous, "abcdefgh".repeat(15), (1024, 1024), (512, 512);
string_alternating_wrapped, "abcdefgh".repeat(15), (1024, 384), (512, 192);
}

#[bench]
fn bench_clone_from_u64_from_empty(b: &mut Bencher) {
do_bench_clone_from_empty(b, 42u64, (1024, 1024));
}

#[bench]
fn bench_clone_from_string_from_empty(b: &mut Bencher) {
do_bench_clone_from_empty(b, "abcdefgh".repeat(15), (1024, 1024));
}

#[bench]
fn bench_iter_1000(b: &mut Bencher) {
let ring: VecDeque<_> = (0..1000).collect();
Expand Down
1 change: 1 addition & 0 deletions library/alloctests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#![feature(test)]
#![feature(thin_box)]
#![feature(titlecase)]
#![feature(trivial_clone)]
#![feature(trusted_len)]
#![feature(try_reserve_kind)]
#![feature(try_with_capacity)]
Expand Down
Loading
Loading