diff --git a/benches/smallvec.rs b/benches/smallvec.rs index 504ffaf..1defc7f 100644 --- a/benches/smallvec.rs +++ b/benches/smallvec.rs @@ -3,27 +3,30 @@ //! All the following commentary is based on the latest nightly at the time: //! rustc 1.55.0 (c8dfcfe04 2021-09-06). //! -//! Some of these benchmarks are just a few instructions, so we put our own for loop inside -//! the criterion::Bencher::iter call. This seems to improve the stability of measurements, and it -//! has the wonderful side effect of making the emitted assembly easier to follow. Some of these -//! benchmarks are totally inlined so that there are no calls at all in the hot path, so finding +//! Some of these benchmarks are just a few instructions, so we put our own for +//! loop inside the criterion::Bencher::iter call. This seems to improve the +//! stability of measurements, and it has the wonderful side effect of making +//! the emitted assembly easier to follow. Some of these benchmarks are totally +//! inlined so that there are no calls at all in the hot path, so finding //! this for loop is an easy way to find your way around the emitted assembly. //! -//! The clear method is cheaper to call for arrays of elements without a Drop impl, so wherever -//! possible we reuse a single object in the benchmark loop, with a clear + black_box on each -//! iteration in an attempt to not make that visible to the optimizer. +//! The clear method is cheaper to call for arrays of elements without a Drop +//! impl, so wherever possible we reuse a single object in the benchmark loop, +//! with a clear + black_box on each iteration in an attempt to not make that +//! visible to the optimizer. //! -//! We always call black_box(&v), instead of v = black_box(v) because the latter does a move of the -//! inline array, which is linear in the size of the array and thus varies based on the array type -//! being benchmarked, and this move can be more expensive than the function we're trying to -//! benchmark. -//! -//! We also black_box the input to each method call. This has a significant effect on the assembly -//! emitted, for example if we do not black_box the range we iterate over in the ::push benchmarks, -//! the loop is unrolled. It's not entirely clear if it's better to black_box the iterator that -//! yields the items being pushed, or to black_box at a deeper level: v.push(black_box(i)) for -//! example. Anecdotally, it seems like the latter approach produces unreasonably bad assembly. +//! We always call black_box(&v), instead of v = black_box(v) because the latter +//! does a move of the inline array, which is linear in the size of the array +//! and thus varies based on the array type being benchmarked, and this move can +//! be more expensive than the function we're trying to benchmark. //! +//! We also black_box the input to each method call. This has a significant +//! effect on the assembly emitted, for example if we do not black_box the range +//! we iterate over in the ::push benchmarks, the loop is unrolled. It's not +//! entirely clear if it's better to black_box the iterator that yields the +//! items being pushed, or to black_box at a deeper level: v.push(black_box(i)) +//! for example. Anecdotally, it seems like the latter approach produces +//! unreasonably bad assembly. use criterion::{black_box, criterion_group, criterion_main, Criterion}; use smallvec::SmallVec; diff --git a/rustfmt.toml b/rustfmt.toml index 777bb3d..e312d17 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,7 +1,7 @@ # Stable edition = "2018" -fn_args_layout = "Compressed" +fn_params_layout = "Compressed" max_width = 80 tab_spaces = 2 use_field_init_shorthand = true @@ -11,4 +11,4 @@ use_small_heuristics = "Max" # Unstable format_code_in_doc_comments = true wrap_comments = true -imports_granularity="Crate" +imports_granularity = "Crate" diff --git a/src/tinyvec.rs b/src/tinyvec.rs index 953a47a..1458047 100644 --- a/src/tinyvec.rs +++ b/src/tinyvec.rs @@ -528,6 +528,65 @@ impl TinyVec { TinyVec::Heap(Vec::with_capacity(cap)) } } + + /// Converts a `TinyVec<[T; N]>` into a `Box<[T]>`. + /// + /// - For `TinyVec::Heap(Vec)`, it takes the `Vec` and converts it into + /// a `Box<[T]>` without heap reallocation. + /// - For `TinyVec::Inline(inner_data)`, it first converts the `inner_data` to + /// `Vec`, then into a `Box<[T]>`. Requiring only a single heap + /// allocation. + /// + /// ## Example + /// + /// ``` + /// use core::mem::size_of_val as mem_size_of; + /// use tinyvec::TinyVec; + /// + /// // Initialize TinyVec with 256 elements (exceeding inline capacity) + /// let v: TinyVec<[_; 128]> = (0u8..=255).collect(); + /// + /// assert!(v.is_heap()); + /// assert_eq!(mem_size_of(&v), 136); // mem size of TinyVec<[u8; N]>: N+8 + /// assert_eq!(v.len(), 256); + /// + /// let boxed = v.into_boxed_slice(); + /// assert_eq!(mem_size_of(&boxed), 16); // mem size of Box<[u8]>: 16 bytes (fat pointer) + /// assert_eq!(boxed.len(), 256); + /// ``` + #[inline] + #[must_use] + pub fn into_boxed_slice(self) -> alloc::boxed::Box<[A::Item]> { + self.into_vec().into_boxed_slice() + } + + /// Converts a `TinyVec<[T; N]>` into a `Vec`. + /// + /// `v.into_vec()` is equivalent to `Into::>::into(v)`. + /// + /// - For `TinyVec::Inline(_)`, `.into_vec()` **does not** offer a performance + /// advantage over `.to_vec()`. + /// - For `TinyVec::Heap(vec_data)`, `.into_vec()` will take `vec_data` + /// without heap reallocation. + /// + /// ## Example + /// + /// ``` + /// use tinyvec::TinyVec; + /// + /// let v = TinyVec::from([0u8; 8]); + /// let v2 = v.clone(); + /// + /// let vec = v.into_vec(); + /// let vec2: Vec<_> = v2.into(); + /// + /// assert_eq!(vec, vec2); + /// ``` + #[inline] + #[must_use] + pub fn into_vec(self) -> Vec { + self.into() + } } impl TinyVec { @@ -1332,6 +1391,61 @@ impl FromIterator for TinyVec { } } +impl Into> for TinyVec { + /// Converts a `TinyVec` into a `Vec`. + /// + /// ## Examples + /// + /// ### Inline to Vec + /// + /// For `TinyVec::Inline(_)`, + /// `.into()` **does not** offer a performance advantage over `.to_vec()`. + /// + /// ``` + /// use core::mem::size_of_val as mem_size_of; + /// use tinyvec::TinyVec; + /// + /// let v = TinyVec::from([0u8; 128]); + /// assert_eq!(mem_size_of(&v), 136); + /// + /// let vec: Vec<_> = v.into(); + /// assert_eq!(mem_size_of(&vec), 24); + /// ``` + /// + /// ### Heap into Vec + /// + /// For `TinyVec::Heap(vec_data)`, + /// `.into()` will take `vec_data` without heap reallocation. + /// + /// ``` + /// use core::{ + /// any::type_name_of_val as type_of, mem::size_of_val as mem_size_of, + /// }; + /// use tinyvec::TinyVec; + /// + /// const fn from_heap(owned: Vec) -> TinyVec<[T; 1]> { + /// TinyVec::Heap(owned) + /// } + /// + /// let v = from_heap(vec![0u8; 128]); + /// assert_eq!(v.len(), 128); + /// assert_eq!(mem_size_of(&v), 24); + /// assert!(type_of(&v).ends_with("TinyVec<[u8; 1]>")); + /// + /// let vec: Vec<_> = v.into(); + /// assert_eq!(mem_size_of(&vec), 24); + /// assert!(type_of(&vec).ends_with("Vec")); + /// ``` + #[inline] + #[must_use] + fn into(self) -> Vec { + match self { + Self::Heap(inner) => inner, + Self::Inline(mut inner) => inner.drain_to_vec(), + } + } +} + /// Iterator for consuming an `TinyVec` and returning owned elements. #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub enum TinyVecIterator {