Skip to content
Merged
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
37 changes: 20 additions & 17 deletions benches/smallvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,4 +11,4 @@ use_small_heuristics = "Max"
# Unstable
format_code_in_doc_comments = true
wrap_comments = true
imports_granularity="Crate"
imports_granularity = "Crate"
114 changes: 114 additions & 0 deletions src/tinyvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,65 @@ impl<A: Array> TinyVec<A> {
TinyVec::Heap(Vec::with_capacity(cap))
}
}

/// Converts a `TinyVec<[T; N]>` into a `Box<[T]>`.
///
/// - For `TinyVec::Heap(Vec<T>)`, it takes the `Vec<T>` and converts it into
/// a `Box<[T]>` without heap reallocation.
/// - For `TinyVec::Inline(inner_data)`, it first converts the `inner_data` to
/// `Vec<T>`, 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<T>`.
///
/// `v.into_vec()` is equivalent to `Into::<Vec<_>>::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<A::Item> {
self.into()
}
}

impl<A: Array> TinyVec<A> {
Expand Down Expand Up @@ -1332,6 +1391,61 @@ impl<A: Array> FromIterator<A::Item> for TinyVec<A> {
}
}

impl<A: Array> Into<Vec<A::Item>> for TinyVec<A> {
/// 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<T: Default>(owned: Vec<T>) -> 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<u8>"));
/// ```
#[inline]
#[must_use]
fn into(self) -> Vec<A::Item> {
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<A: Array> {
Expand Down