Skip to content

Commit 91fa732

Browse files
committed
splice() method and Splice iterator
This implements splicing. This allows for insert/replace operations in HeaderVecs. The semantic is the same as the stdlib splice. But we use a slightly different algorithm internally: * The stdlib Splice collects excess replace_with elements in a temporary vector and later fills this in. in some/worst cases this may cause the tail to be moved arouind two times. * Our implementation pushes objects from `replace_with` directly onto the source HeaderVec If tail elements would be overwritten they are stashed on a temporary vector. Only these stashed elements are moved twice, the remainder of the tail is moved only once. Note: moving elements because of reallocation is not accounted here. In both implementations this can happen equally bad. The stdlib can mitigate that by this by some unstable features. We try hard to do it as best as possible. Benchmarks will follow.
1 parent 3e9047e commit 91fa732

2 files changed

Lines changed: 262 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ mod drain;
2626
#[cfg(feature = "std")]
2727
pub use drain::Drain;
2828

29+
mod splice;
30+
#[cfg(feature = "std")]
31+
pub use splice::Splice;
32+
2933
// To implement std/Vec compatibility we would need a few nightly features.
3034
// For the time being we just reimplement them here until they become stabilized.
3135
#[cfg(feature = "std")]
@@ -862,6 +866,84 @@ impl<H, T> HeaderVec<H, T> {
862866
}
863867
}
864868
}
869+
870+
/// Creates a splicing iterator that replaces the specified range in the vector
871+
/// with the given `replace_with` iterator and yields the removed items.
872+
/// `replace_with` does not need to be the same length as `range`.
873+
///
874+
/// `range` is removed even if the iterator is not consumed until the end.
875+
///
876+
/// It is unspecified how many elements are removed from the vector
877+
/// if the `Splice` value is leaked.
878+
///
879+
/// The input iterator `replace_with` is only consumed when the `Splice` value is dropped.
880+
///
881+
/// This is optimal if:
882+
///
883+
/// * The tail (elements in the vector after `range`) is empty,
884+
/// * or `replace_with` yields fewer or equal elements than `range`’s length
885+
/// * or the lower bound of its `size_hint()` is exact.
886+
///
887+
/// Otherwise, a temporary vector is allocated to store the tail elements which are in the way.
888+
///
889+
/// # Panics
890+
///
891+
/// Panics if the starting point is greater than the end point or if
892+
/// the end point is greater than the length of the vector.
893+
///
894+
/// # Examples
895+
///
896+
/// ```
897+
/// use header_vec::HeaderVec;
898+
/// let mut hv: HeaderVec<(), i32> = HeaderVec::from([1, 2, 3, 4]);
899+
/// let new = [7, 8, 9];
900+
/// let u: Vec<_> = hv.splice(1..3, new).collect();
901+
/// assert_eq!(hv.as_slice(), [1, 7, 8, 9, 4]);
902+
/// assert_eq!(u, [2, 3]);
903+
/// ```
904+
#[inline]
905+
pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, H, I::IntoIter>
906+
where
907+
R: RangeBounds<usize>,
908+
I: IntoIterator<Item = T>,
909+
{
910+
self.splice_internal(range, replace_with, None)
911+
}
912+
913+
/// Creates a splicing iterator like [`splice()`].
914+
/// This method must be used when `HeaderVecWeak` are used. It takes a closure that is responsible for
915+
/// updating the weak references as additional parameter.
916+
#[inline]
917+
pub fn splice_with_weakfix<'a, R, I>(
918+
&'a mut self,
919+
range: R,
920+
replace_with: I,
921+
weak_fixup: WeakFixupFn<'a>,
922+
) -> Splice<'a, H, I::IntoIter>
923+
where
924+
R: RangeBounds<usize>,
925+
I: IntoIterator<Item = T>,
926+
{
927+
self.splice_internal(range, replace_with, Some(weak_fixup))
928+
}
929+
930+
#[inline(always)]
931+
fn splice_internal<'a, R, I>(
932+
&'a mut self,
933+
range: R,
934+
replace_with: I,
935+
weak_fixup: Option<WeakFixupFn<'a>>,
936+
) -> Splice<'a, H, I::IntoIter>
937+
where
938+
R: RangeBounds<usize>,
939+
I: IntoIterator<Item = T>,
940+
{
941+
Splice {
942+
drain: self.drain(range),
943+
replace_with: replace_with.into_iter(),
944+
weak_fixup,
945+
}
946+
}
865947
}
866948

867949
impl<H, T> Drop for HeaderVec<H, T> {

src/splice.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#![cfg(feature = "std")]
2+
3+
use core::{any::type_name, fmt, ptr};
4+
5+
use crate::{Drain, WeakFixupFn};
6+
7+
/// A splicing iterator for a `HeaderVec`.
8+
///
9+
/// This struct is created by [`Vec::splice()`].
10+
/// See its documentation for more.
11+
///
12+
/// # Example
13+
///
14+
/// ```
15+
/// # use header_vec::HeaderVec;
16+
/// let mut hv: HeaderVec<(), _> = HeaderVec::from([0, 1, 2]);
17+
/// let new = [7, 8];
18+
/// let iter = hv.splice(1.., new);
19+
/// ```
20+
pub struct Splice<'a, H, I: Iterator + 'a> {
21+
pub(super) drain: Drain<'a, H, I::Item>,
22+
pub(super) replace_with: I,
23+
pub(super) weak_fixup: Option<WeakFixupFn<'a>>,
24+
}
25+
26+
impl<H, I: Iterator> Iterator for Splice<'_, H, I> {
27+
type Item = I::Item;
28+
29+
fn next(&mut self) -> Option<Self::Item> {
30+
self.drain.next()
31+
}
32+
33+
fn size_hint(&self) -> (usize, Option<usize>) {
34+
self.drain.size_hint()
35+
}
36+
}
37+
38+
impl<H, I: Iterator> Splice<'_, H, I> {
39+
/// Not a standard function, might be useful nevertheless, we use it in tests.
40+
pub fn drained_slice(&self) -> &[I::Item] {
41+
self.drain.as_slice()
42+
}
43+
}
44+
45+
impl<H, I: Iterator> DoubleEndedIterator for Splice<'_, H, I> {
46+
fn next_back(&mut self) -> Option<Self::Item> {
47+
self.drain.next_back()
48+
}
49+
}
50+
51+
impl<H, I: Iterator> ExactSizeIterator for Splice<'_, H, I> {}
52+
53+
impl<H, I> fmt::Debug for Splice<'_, H, I>
54+
where
55+
I: Iterator + fmt::Debug,
56+
I::Item: fmt::Debug,
57+
{
58+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59+
f.debug_struct(&format!(
60+
"Splice<{}, {}>",
61+
type_name::<H>(),
62+
type_name::<I>()
63+
))
64+
.field("drain", &self.drain.as_slice())
65+
.field("replace_with", &self.replace_with)
66+
.field("weak_fixup", &self.weak_fixup.is_some())
67+
.finish()
68+
}
69+
}
70+
71+
impl<H, I: Iterator> Drop for Splice<'_, H, I> {
72+
#[track_caller]
73+
fn drop(&mut self) {
74+
self.drain.by_ref().for_each(drop);
75+
// At this point draining is done and the only remaining tasks are splicing
76+
// and moving things into the final place.
77+
// Which means we can replace the slice::Iter with pointers that won't point to deallocated
78+
// memory, so that Drain::drop is still allowed to call iter.len(), otherwise it would break
79+
// the ptr.sub_ptr contract.
80+
self.drain.iter = [].iter();
81+
82+
// We will use the replace_with iterator to append elements in place on self.drain.vec.
83+
// When this hits the tail then elements are moved from the tail to tmp_tail.
84+
// When the tail is or becomes empty by that, then the remaining elements can be extended to the vec.
85+
//
86+
// Finally:
87+
// Then have continuous elements in the vec: |head|replace_with|(old_tail|)spare_capacity|.
88+
// The old tail needs to be moved to its final destination.
89+
// Perhaps making space for the elements in the tmp_tail.
90+
let mut tmp_tail = Vec::new();
91+
92+
unsafe {
93+
let vec = self.drain.vec.as_mut();
94+
loop {
95+
if self.drain.tail_len() == 0 {
96+
// If the tail is empty, we can just extend the vector with the remaining elements.
97+
// but we may have stashed some tmp_tail away and should reserve for that.
98+
// PLANNED: should become 'extend_reserve()'
99+
vec.reserve_intern(
100+
self.replace_with.size_hint().0 + tmp_tail.len(),
101+
false,
102+
&mut self.weak_fixup,
103+
);
104+
vec.extend(self.replace_with.by_ref());
105+
// in case the size_hint was not exact (or returned 0) we need to reserve for the tmp_tail
106+
// in most cases this will not allocate. later we expect that we have this space reserved.
107+
vec.reserve_intern(tmp_tail.len(), false, &mut self.weak_fixup);
108+
break;
109+
} else if let Some(next) = self.replace_with.next() {
110+
if vec.len_exact() >= self.drain.tail_start && self.drain.tail_len() > 0 {
111+
// move one element from the tail to the tmp_tail
112+
// We reserve for as much elements are hinted by replace_with or the remaining tail,
113+
// whatever is smaller.
114+
tmp_tail
115+
.reserve(self.replace_with.size_hint().0.min(self.drain.tail_len()));
116+
tmp_tail.push(ptr::read(vec.as_ptr().add(self.drain.tail_start)));
117+
self.drain.tail_start += 1;
118+
}
119+
120+
// since we overwrite the old tail here this will never reallocate.
121+
// PLANNED: vec.push_within_capacity().unwrap_unchecked()
122+
vec.push(next);
123+
} else {
124+
// replace_with is depleted
125+
break;
126+
}
127+
}
128+
129+
let tail_len = self.drain.tail_len();
130+
if tail_len > 0 {
131+
// In case we need to shift the tail farther back we need to reserve space for that.
132+
// Reserve needs to preserve the tail we have, thus we temporarily set the length to the
133+
// tail_end and then restore it after the reserve.
134+
let old_len = vec.len_exact();
135+
vec.set_len(self.drain.tail_end);
136+
vec.reserve_intern(tmp_tail.len(), false, &mut self.weak_fixup);
137+
vec.set_len(old_len);
138+
139+
// now we can move the tail around
140+
ptr::copy(
141+
vec.as_ptr().add(self.drain.tail_start),
142+
vec.as_mut_ptr().add(vec.len_exact() + tmp_tail.len()),
143+
tail_len,
144+
);
145+
146+
// all elements are moved from the tail, ensure that Drain drop does nothing.
147+
// PLANNED: eventually we may not need use Drain here
148+
self.drain.tail_start = self.drain.tail_end;
149+
}
150+
151+
let tmp_tail_len = tmp_tail.len();
152+
if !tmp_tail.is_empty() {
153+
// When we stashed tail elements to tmp_tail, then fill the gap
154+
tmp_tail.set_len(0);
155+
ptr::copy_nonoverlapping(
156+
tmp_tail.as_ptr(),
157+
vec.as_mut_ptr().add(vec.len_exact()),
158+
tmp_tail_len,
159+
);
160+
}
161+
162+
// finally fix the vec length
163+
let new_len = vec.len_exact() + tmp_tail_len + tail_len;
164+
vec.set_len(new_len);
165+
}
166+
167+
// IDEA: implement and benchmark batched copying. This leaves a gap in front of the tails which
168+
// needs to be filled before resizing.
169+
// Batch size:
170+
// Moving one element per iteration to the tmp_tail is not efficient to make space for
171+
// a element from the replace_with. Thus we determine a number of elements that we
172+
// transfer in a batch to the tmp_tail. We compute the batch size to be roughly 4kb
173+
// (Common page size on many systems) (or I::Item, whatever is larger) or the size of
174+
// the tail when it is smaller. The later ensure that we do a single reserve with the
175+
// minimum space needed when the tail is smaller than a batch would be .
176+
// let batch_size = (4096 / std::mem::size_of::<I::Item>())
177+
// .max(1)
178+
// .min(self.drain.tail_len);
179+
}
180+
}

0 commit comments

Comments
 (0)