From 0314c039931b8f8d2e32959365c4cc79830694f3 Mon Sep 17 00:00:00 2001 From: Karl Meakin Date: Thu, 3 Aug 2023 13:24:59 +0100 Subject: [PATCH] Optimize `ecovec![]` macro (#29) LLVM is not smart enough to eliminate calls to `EcoVec::reserve()` even when they are unecessary thanks to earlier calls to `EcoVec::reserve()` or `EcoVec::with_capacity()`. This patch elimates them when the `ecovec![]` macro is used. There may be other unnecessary calls which can be elimated in future patches. `ecovec![1, 2, 3]` results in 3 unecessary calls to `EcoVec::reserve()`. Fixed by forwarding to the `From<[T; N]>` impl. `ecovec![1; 42]` results in 42 unecessary calls to `EcoVec::reserve()`. Fixed by adding `EcoVec::push_unchecked()` function for when we know resizing is unnecessary. --- src/vec.rs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/vec.rs b/src/vec.rs index a8e81e8..8bc2907 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -23,13 +23,7 @@ use crate::sync::atomic::{self, AtomicUsize, Ordering::*}; macro_rules! eco_vec { () => { $crate::EcoVec::new() }; ($elem:expr; $n:expr) => { $crate::EcoVec::from_elem($elem, $n) }; - ($($value:expr),+ $(,)?) => {{ - let capacity = 0 $(+ $crate::eco_vec!(@count $value))*; - let mut vec = $crate::EcoVec::with_capacity(capacity); - $(vec.push($value);)* - vec - }}; - (@count $value:expr) => { 1 }; + ($($value:expr),+ $(,)?) => { $crate::EcoVec::from([$($value),+]) }; } /// An economical vector with clone-on-write semantics. @@ -214,7 +208,8 @@ impl EcoVec { pub fn from_elem(value: T, n: usize) -> Self { let mut vec = Self::with_capacity(n); for _ in 0..n { - vec.push(value.clone()); + // Safety: we just called `EcoVec::with_capacity()` + unsafe { vec.push_unchecked(value.clone()) } } vec } @@ -241,12 +236,27 @@ impl EcoVec { // Ensure unique ownership and grow the vector if necessary. self.reserve((self.len == self.capacity()) as usize); + // Safety: we just called `EcoVec::reserve()` + unsafe { self.push_unchecked(value) } + } + + /// Add a value at the end of the vector, without reallocating. + /// + /// # Safety + /// Caller must ensure that `self.len < self.capacity()` and + /// `self.is_unique()` hold, such as by calling `EcoVec::reserve()`, or + /// `EcoVec::with_capacity()`. + #[inline] + unsafe fn push_unchecked(&mut self, value: T) { + debug_assert!(self.len < self.capacity()); + debug_assert!(self.is_unique()); + unsafe { // Safety: - // - The reference count is `1` because of `reserve`. + // - Caller must ensure that reference count is `1`. // - The pointer returned by `data_mut()` is valid for `capacity` // writes. - // - Due to the `reserve` call, `len < capacity`. + // - Caller must ensure that `len < capacity`. // - Thus, `data_mut() + len` is valid for one write. ptr::write(self.data_mut().add(self.len), value);