Skip to content

Commit 31c6f67

Browse files
authored
feat(TinyVec): add into_vec() & into_boxed_slice() (#206)
* chore(rustfmt): deprecated `fn_args_layout` -> `fn_args_layout` See also: <rust-lang/rustfmt#4149> * feat(TinyVec): add `into_vec()` & `into_boxed_slice()` * docs(into_boxed_slice): `Box<T>` -> `Box<[T]>` * docs(into_boxed_slice): smaller array sizes * docs(tinyvec array sizes): 120 -> 128 * docs(into_boxed_slice): range (`..`) -> `..=` * docs(TinyVec::into_vec): `into()` -> `.into_vec()`
1 parent 9726c64 commit 31c6f67

File tree

3 files changed

+136
-19
lines changed

3 files changed

+136
-19
lines changed

Diff for: benches/smallvec.rs

+20-17
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,30 @@
33
//! All the following commentary is based on the latest nightly at the time:
44
//! rustc 1.55.0 (c8dfcfe04 2021-09-06).
55
//!
6-
//! Some of these benchmarks are just a few instructions, so we put our own for loop inside
7-
//! the criterion::Bencher::iter call. This seems to improve the stability of measurements, and it
8-
//! has the wonderful side effect of making the emitted assembly easier to follow. Some of these
9-
//! benchmarks are totally inlined so that there are no calls at all in the hot path, so finding
6+
//! Some of these benchmarks are just a few instructions, so we put our own for
7+
//! loop inside the criterion::Bencher::iter call. This seems to improve the
8+
//! stability of measurements, and it has the wonderful side effect of making
9+
//! the emitted assembly easier to follow. Some of these benchmarks are totally
10+
//! inlined so that there are no calls at all in the hot path, so finding
1011
//! this for loop is an easy way to find your way around the emitted assembly.
1112
//!
12-
//! The clear method is cheaper to call for arrays of elements without a Drop impl, so wherever
13-
//! possible we reuse a single object in the benchmark loop, with a clear + black_box on each
14-
//! iteration in an attempt to not make that visible to the optimizer.
13+
//! The clear method is cheaper to call for arrays of elements without a Drop
14+
//! impl, so wherever possible we reuse a single object in the benchmark loop,
15+
//! with a clear + black_box on each iteration in an attempt to not make that
16+
//! visible to the optimizer.
1517
//!
16-
//! We always call black_box(&v), instead of v = black_box(v) because the latter does a move of the
17-
//! inline array, which is linear in the size of the array and thus varies based on the array type
18-
//! being benchmarked, and this move can be more expensive than the function we're trying to
19-
//! benchmark.
20-
//!
21-
//! We also black_box the input to each method call. This has a significant effect on the assembly
22-
//! emitted, for example if we do not black_box the range we iterate over in the ::push benchmarks,
23-
//! the loop is unrolled. It's not entirely clear if it's better to black_box the iterator that
24-
//! yields the items being pushed, or to black_box at a deeper level: v.push(black_box(i)) for
25-
//! example. Anecdotally, it seems like the latter approach produces unreasonably bad assembly.
18+
//! We always call black_box(&v), instead of v = black_box(v) because the latter
19+
//! does a move of the inline array, which is linear in the size of the array
20+
//! and thus varies based on the array type being benchmarked, and this move can
21+
//! be more expensive than the function we're trying to benchmark.
2622
//!
23+
//! We also black_box the input to each method call. This has a significant
24+
//! effect on the assembly emitted, for example if we do not black_box the range
25+
//! we iterate over in the ::push benchmarks, the loop is unrolled. It's not
26+
//! entirely clear if it's better to black_box the iterator that yields the
27+
//! items being pushed, or to black_box at a deeper level: v.push(black_box(i))
28+
//! for example. Anecdotally, it seems like the latter approach produces
29+
//! unreasonably bad assembly.
2730
2831
use criterion::{black_box, criterion_group, criterion_main, Criterion};
2932
use smallvec::SmallVec;

Diff for: rustfmt.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# Stable
33
edition = "2018"
4-
fn_args_layout = "Compressed"
4+
fn_params_layout = "Compressed"
55
max_width = 80
66
tab_spaces = 2
77
use_field_init_shorthand = true
@@ -11,4 +11,4 @@ use_small_heuristics = "Max"
1111
# Unstable
1212
format_code_in_doc_comments = true
1313
wrap_comments = true
14-
imports_granularity="Crate"
14+
imports_granularity = "Crate"

Diff for: src/tinyvec.rs

+114
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,65 @@ impl<A: Array> TinyVec<A> {
528528
TinyVec::Heap(Vec::with_capacity(cap))
529529
}
530530
}
531+
532+
/// Converts a `TinyVec<[T; N]>` into a `Box<[T]>`.
533+
///
534+
/// - For `TinyVec::Heap(Vec<T>)`, it takes the `Vec<T>` and converts it into
535+
/// a `Box<[T]>` without heap reallocation.
536+
/// - For `TinyVec::Inline(inner_data)`, it first converts the `inner_data` to
537+
/// `Vec<T>`, then into a `Box<[T]>`. Requiring only a single heap
538+
/// allocation.
539+
///
540+
/// ## Example
541+
///
542+
/// ```
543+
/// use core::mem::size_of_val as mem_size_of;
544+
/// use tinyvec::TinyVec;
545+
///
546+
/// // Initialize TinyVec with 256 elements (exceeding inline capacity)
547+
/// let v: TinyVec<[_; 128]> = (0u8..=255).collect();
548+
///
549+
/// assert!(v.is_heap());
550+
/// assert_eq!(mem_size_of(&v), 136); // mem size of TinyVec<[u8; N]>: N+8
551+
/// assert_eq!(v.len(), 256);
552+
///
553+
/// let boxed = v.into_boxed_slice();
554+
/// assert_eq!(mem_size_of(&boxed), 16); // mem size of Box<[u8]>: 16 bytes (fat pointer)
555+
/// assert_eq!(boxed.len(), 256);
556+
/// ```
557+
#[inline]
558+
#[must_use]
559+
pub fn into_boxed_slice(self) -> alloc::boxed::Box<[A::Item]> {
560+
self.into_vec().into_boxed_slice()
561+
}
562+
563+
/// Converts a `TinyVec<[T; N]>` into a `Vec<T>`.
564+
///
565+
/// `v.into_vec()` is equivalent to `Into::<Vec<_>>::into(v)`.
566+
///
567+
/// - For `TinyVec::Inline(_)`, `.into_vec()` **does not** offer a performance
568+
/// advantage over `.to_vec()`.
569+
/// - For `TinyVec::Heap(vec_data)`, `.into_vec()` will take `vec_data`
570+
/// without heap reallocation.
571+
///
572+
/// ## Example
573+
///
574+
/// ```
575+
/// use tinyvec::TinyVec;
576+
///
577+
/// let v = TinyVec::from([0u8; 8]);
578+
/// let v2 = v.clone();
579+
///
580+
/// let vec = v.into_vec();
581+
/// let vec2: Vec<_> = v2.into();
582+
///
583+
/// assert_eq!(vec, vec2);
584+
/// ```
585+
#[inline]
586+
#[must_use]
587+
pub fn into_vec(self) -> Vec<A::Item> {
588+
self.into()
589+
}
531590
}
532591

533592
impl<A: Array> TinyVec<A> {
@@ -1332,6 +1391,61 @@ impl<A: Array> FromIterator<A::Item> for TinyVec<A> {
13321391
}
13331392
}
13341393

1394+
impl<A: Array> Into<Vec<A::Item>> for TinyVec<A> {
1395+
/// Converts a `TinyVec` into a `Vec`.
1396+
///
1397+
/// ## Examples
1398+
///
1399+
/// ### Inline to Vec
1400+
///
1401+
/// For `TinyVec::Inline(_)`,
1402+
/// `.into()` **does not** offer a performance advantage over `.to_vec()`.
1403+
///
1404+
/// ```
1405+
/// use core::mem::size_of_val as mem_size_of;
1406+
/// use tinyvec::TinyVec;
1407+
///
1408+
/// let v = TinyVec::from([0u8; 128]);
1409+
/// assert_eq!(mem_size_of(&v), 136);
1410+
///
1411+
/// let vec: Vec<_> = v.into();
1412+
/// assert_eq!(mem_size_of(&vec), 24);
1413+
/// ```
1414+
///
1415+
/// ### Heap into Vec
1416+
///
1417+
/// For `TinyVec::Heap(vec_data)`,
1418+
/// `.into()` will take `vec_data` without heap reallocation.
1419+
///
1420+
/// ```
1421+
/// use core::{
1422+
/// any::type_name_of_val as type_of, mem::size_of_val as mem_size_of,
1423+
/// };
1424+
/// use tinyvec::TinyVec;
1425+
///
1426+
/// const fn from_heap<T: Default>(owned: Vec<T>) -> TinyVec<[T; 1]> {
1427+
/// TinyVec::Heap(owned)
1428+
/// }
1429+
///
1430+
/// let v = from_heap(vec![0u8; 128]);
1431+
/// assert_eq!(v.len(), 128);
1432+
/// assert_eq!(mem_size_of(&v), 24);
1433+
/// assert!(type_of(&v).ends_with("TinyVec<[u8; 1]>"));
1434+
///
1435+
/// let vec: Vec<_> = v.into();
1436+
/// assert_eq!(mem_size_of(&vec), 24);
1437+
/// assert!(type_of(&vec).ends_with("Vec<u8>"));
1438+
/// ```
1439+
#[inline]
1440+
#[must_use]
1441+
fn into(self) -> Vec<A::Item> {
1442+
match self {
1443+
Self::Heap(inner) => inner,
1444+
Self::Inline(mut inner) => inner.drain_to_vec(),
1445+
}
1446+
}
1447+
}
1448+
13351449
/// Iterator for consuming an `TinyVec` and returning owned elements.
13361450
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
13371451
pub enum TinyVecIterator<A: Array> {

0 commit comments

Comments
 (0)