From 17fc02b10fab3355407e5391d11a4f65e1f0536b Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sat, 30 Nov 2024 05:25:23 +0000 Subject: [PATCH 1/2] Use `iter::repeat_n` to implement `Vec::extend_with` This replaces the manual `Vec::extend_with` implementation with `iter::repeat_n` and `Vec::extend_trusted`. This simplifies the code and gets LLVM to remove the special case for the last element when `T` is trivial to clone. --- library/alloc/src/vec/mod.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 2adce8d270398..2fdc83b9d534a 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -3448,31 +3448,7 @@ impl Vec { #[cfg(not(no_global_oom_handling))] /// Extend the vector by `n` clones of value. fn extend_with(&mut self, n: usize, value: T) { - self.reserve(n); - - unsafe { - let mut ptr = self.as_mut_ptr().add(self.len()); - // Use SetLenOnDrop to work around bug where compiler - // might not realize the store through `ptr` through self.set_len() - // don't alias. - let mut local_len = SetLenOnDrop::new(&mut self.len); - - // Write all elements except the last one - for _ in 1..n { - ptr::write(ptr, value.clone()); - ptr = ptr.add(1); - // Increment the length in every step in case clone() panics - local_len.increment_len(1); - } - - if n > 0 { - // We can write the last element directly without cloning needlessly - ptr::write(ptr, value); - local_len.increment_len(1); - } - - // len set by scope guard - } + self.extend_trusted(iter::repeat_n(value, n)); } } From 164c6cf48b10af19f4044c6f0cce77378c3a3716 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sat, 26 Jul 2025 05:05:00 +0200 Subject: [PATCH 2/2] Specialize `Vec::extend_with` for `T: TrivialClone` After running perf on the previous commit, it was found that the compile-time regression was too significant to ignore. This introduces a specialized, simplified implementation for `T: TrivialClone` that will reduce the perf regression. --- library/alloc/src/vec/mod.rs | 8 ++++- library/alloc/src/vec/spec_extend_with.rs | 37 +++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 library/alloc/src/vec/spec_extend_with.rs diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 2fdc83b9d534a..2f2ff1b04e5c7 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -172,6 +172,12 @@ use self::spec_extend::SpecExtend; #[cfg(not(no_global_oom_handling))] mod spec_extend; +#[cfg(not(no_global_oom_handling))] +use self::spec_extend_with::SpecExtendWith; + +#[cfg(not(no_global_oom_handling))] +mod spec_extend_with; + /// A contiguous growable array type, written as `Vec`, short for 'vector'. /// /// # Examples @@ -3448,7 +3454,7 @@ impl Vec { #[cfg(not(no_global_oom_handling))] /// Extend the vector by `n` clones of value. fn extend_with(&mut self, n: usize, value: T) { - self.extend_trusted(iter::repeat_n(value, n)); + >::spec_extend_with(self, n, value); } } diff --git a/library/alloc/src/vec/spec_extend_with.rs b/library/alloc/src/vec/spec_extend_with.rs new file mode 100644 index 0000000000000..d56537490b34e --- /dev/null +++ b/library/alloc/src/vec/spec_extend_with.rs @@ -0,0 +1,37 @@ +use core::clone::TrivialClone; +use core::mem::MaybeUninit; +use core::{iter, ptr}; + +use super::Vec; +use crate::alloc::Allocator; + +// Specialization trait used for Vec::extend_with +pub(super) trait SpecExtendWith { + fn spec_extend_with(&mut self, n: usize, value: T); +} + +impl SpecExtendWith for Vec { + default fn spec_extend_with(&mut self, n: usize, value: T) { + self.extend_trusted(iter::repeat_n(value, n)); + } +} + +impl SpecExtendWith for Vec { + fn spec_extend_with(&mut self, n: usize, value: T) { + let len = self.len(); + self.reserve(n); + let unfilled = self.spare_capacity_mut(); + + // SAFETY: the above `reserve` call guarantees `n` to be in bounds. + let unfilled = unsafe { unfilled.get_unchecked_mut(..n) }; + + // SAFETY: because `T` is `TrivialClone`, this is equivalent to calling + // `T::clone` for every element. Notably, `TrivialClone` also implies + // that the `clone` implementation will not panic, so we can avoid + // initialization guards and such. + unfilled.fill_with(|| MaybeUninit::new(unsafe { ptr::read(&value) })); + + // SAFETY: the elements have been initialized above. + unsafe { self.set_len(len + n) } + } +}