From c780fe6b277aea25a8889e292492c4519715c48e Mon Sep 17 00:00:00 2001 From: The 8472 Date: Fri, 26 Jan 2024 00:33:02 +0100 Subject: [PATCH 1/2] document `FromIterator for Vec` allocation behaviors --- library/alloc/src/vec/mod.rs | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index a360c43094666..7c2c7898ce6b1 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2784,6 +2784,51 @@ impl, A: Allocator> IndexMut for Vec { } } +/// Collects an iterator into a Vec, commonly called via [`Iterator::collect()`] +/// +/// # Allocation behavior +/// +/// In general `Vec` does not guarantee any particular grow/allocation stategy. +/// That also applies to this trait impl. +/// +/// **Note:** This section covers implementation details and is therefore exempt from +/// stability guarantees. +/// +/// Vec may use any or none of the following strategies, +/// depending on the supplied iterator: +/// +/// * preallocate based on [`Iterator::size_hint()`] +/// * and panic if the number of items is not outside the provided lower/upper bounds +/// * use an amortized growth strategy similar to `pushing` one item at a time +/// * perform the iteration in-place on the original allocation backing the iterator +/// +/// The last case warrants some attention. It is an optimization that in many cases reduces peak memory +/// consumption and improves cache locality. But when a large number of big, short-lived +/// allocations are created, only a small fraction of their items gets collected, no further use +/// is made of the spare capacity and the resulting `Vec` is moved into a longer-lived structure +/// this can lead to the large allocations having their lifetimes unnecessarily extended which +/// can result in increased memory footprint. +/// +/// In cases where this is an issue the excess capacity can be discard with [`Vec::shrink_to()`], +/// [`Vec::shrink_to_fit()`] or by collecting into [`Box<[T]>`][owned slice] instead which additionally reduces +/// the size of the longlived struct. +/// +/// [owned slice]: Box +/// +/// ```rust +/// # use std::sync::Mutex; +/// static LONG_LIVED: Mutex>> = Mutex::new(Vec::new()); +/// +/// // many short-lived allocations +/// for i in 0..100 { +/// let big_temporary: Vec = (0..1024).collect(); +/// // discard most items +/// let mut result: Vec<_> = big_temporary.into_iter().filter(|i| i % 100 == 0).collect(); +/// // without this a lot of unused capacity might be moved into the global +/// result.shrink_to_fit(); +/// LONG_LIVED.lock().unwrap().push(result); +/// } +/// ``` #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] impl FromIterator for Vec { From 39dc3153c52dc244207e02323584f94e63003281 Mon Sep 17 00:00:00 2001 From: the8472 Date: Sat, 27 Jan 2024 00:12:13 +0100 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Josh Stone --- library/alloc/src/vec/mod.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 7c2c7898ce6b1..b3e5ecc924071 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2788,7 +2788,7 @@ impl, A: Allocator> IndexMut for Vec { /// /// # Allocation behavior /// -/// In general `Vec` does not guarantee any particular grow/allocation stategy. +/// In general `Vec` does not guarantee any particular growth or allocation strategy. /// That also applies to this trait impl. /// /// **Note:** This section covers implementation details and is therefore exempt from @@ -2798,20 +2798,20 @@ impl, A: Allocator> IndexMut for Vec { /// depending on the supplied iterator: /// /// * preallocate based on [`Iterator::size_hint()`] -/// * and panic if the number of items is not outside the provided lower/upper bounds +/// * and panic if the number of items is outside the provided lower/upper bounds /// * use an amortized growth strategy similar to `pushing` one item at a time /// * perform the iteration in-place on the original allocation backing the iterator /// /// The last case warrants some attention. It is an optimization that in many cases reduces peak memory -/// consumption and improves cache locality. But when a large number of big, short-lived -/// allocations are created, only a small fraction of their items gets collected, no further use -/// is made of the spare capacity and the resulting `Vec` is moved into a longer-lived structure -/// this can lead to the large allocations having their lifetimes unnecessarily extended which -/// can result in increased memory footprint. +/// consumption and improves cache locality. But when big, short-lived allocations are created, +/// only a small fraction of their items get collected, no further use is made of the spare capacity +/// and the resulting `Vec` is moved into a longer-lived structure, then this can lead to the large +/// allocations having their lifetimes unnecessarily extended which can result in increased memory +/// footprint. /// -/// In cases where this is an issue the excess capacity can be discard with [`Vec::shrink_to()`], -/// [`Vec::shrink_to_fit()`] or by collecting into [`Box<[T]>`][owned slice] instead which additionally reduces -/// the size of the longlived struct. +/// In cases where this is an issue, the excess capacity can be discarded with [`Vec::shrink_to()`], +/// [`Vec::shrink_to_fit()`] or by collecting into [`Box<[T]>`][owned slice] instead, which additionally reduces +/// the size of the long-lived struct. /// /// [owned slice]: Box /// @@ -2819,8 +2819,7 @@ impl, A: Allocator> IndexMut for Vec { /// # use std::sync::Mutex; /// static LONG_LIVED: Mutex>> = Mutex::new(Vec::new()); /// -/// // many short-lived allocations -/// for i in 0..100 { +/// for i in 0..10 { /// let big_temporary: Vec = (0..1024).collect(); /// // discard most items /// let mut result: Vec<_> = big_temporary.into_iter().filter(|i| i % 100 == 0).collect();