Skip to content

Commit 4512598

Browse files
committedNov 26, 2022
Improve slow path in make_contiguous
1 parent f6f2598 commit 4512598

File tree

2 files changed

+76
-36
lines changed

2 files changed

+76
-36
lines changed
 

‎library/alloc/src/collections/vec_deque/mod.rs

+67-27
Original file line numberDiff line numberDiff line change
@@ -2152,37 +2152,77 @@ impl<T, A: Allocator> VecDeque<T, A> {
21522152

21532153
self.head = tail;
21542154
} else {
2155-
// free is smaller than both head and tail,
2156-
// this means we have to slowly "swap" the tail and the head.
2155+
// ´free` is smaller than both `head_len` and `tail_len`.
2156+
// the general algorithm for this first moves the slices
2157+
// right next to each other and then uses `slice::rotate`
2158+
// to rotate them into place:
21572159
//
2158-
// from: EFGHI...ABCD or HIJK.ABCDEFG
2159-
// to: ABCDEFGHI... or ABCDEFGHIJK.
2160-
let mut left_edge: usize = 0;
2161-
let mut right_edge: usize = head;
2162-
unsafe {
2163-
// The general problem looks like this
2164-
// GHIJKLM...ABCDEF - before any swaps
2165-
// ABCDEFM...GHIJKL - after 1 pass of swaps
2166-
// ABCDEFGHIJM...KL - swap until the left edge reaches the temp store
2167-
// - then restart the algorithm with a new (smaller) store
2168-
// Sometimes the temp store is reached when the right edge is at the end
2169-
// of the buffer - this means we've hit the right order with fewer swaps!
2170-
// E.g
2171-
// EF..ABCD
2172-
// ABCDEF.. - after four only swaps we've finished
2173-
while left_edge < len && right_edge != cap {
2174-
let mut right_offset = 0;
2175-
for i in left_edge..right_edge {
2176-
right_offset = (i - left_edge) % (cap - right_edge);
2177-
let src = right_edge + right_offset;
2178-
ptr::swap(ptr.add(i), ptr.add(src));
2160+
// initially: HIJK..ABCDEFG
2161+
// step 1: ..HIJKABCDEFG
2162+
// step 2: ..ABCDEFGHIJK
2163+
//
2164+
// or:
2165+
//
2166+
// initially: FGHIJK..ABCDE
2167+
// step 1: FGHIJKABCDE..
2168+
// step 2: ABCDEFGHIJK..
2169+
2170+
// pick the shorter of the 2 slices to reduce the amount
2171+
// of memory that needs to be moved around.
2172+
if head_len > tail_len {
2173+
// tail is shorter, so:
2174+
// 1. copy tail forwards
2175+
// 2. rotate used part of the buffer
2176+
// 3. update head to point to the new beginning (which is just `free`)
2177+
2178+
unsafe {
2179+
// if there is no free space in the buffer, then the slices are already
2180+
// right next to each other and we don't need to move any memory.
2181+
if free != 0 {
2182+
// because we only move the tail forward as much as there's free space
2183+
// behind it, we don't overwrite any elements of the head slice, and
2184+
// the slices end up right next to each other.
2185+
self.copy(0, free, tail_len);
21792186
}
2180-
let n_ops = right_edge - left_edge;
2181-
left_edge += n_ops;
2182-
right_edge += right_offset + 1;
2187+
2188+
// We just copied the tail right next to the head slice,
2189+
// so all of the elements in the range are initialized
2190+
let slice = &mut *self.buffer_range(free..self.capacity());
2191+
2192+
// because the deque wasn't contiguous, we know that `tail_len < self.len == slice.len()`,
2193+
// so this will never panic.
2194+
slice.rotate_left(tail_len);
2195+
2196+
// the used part of the buffer now is `free..self.capacity()`, so set
2197+
// `head` to the beginning of that range.
2198+
self.head = free;
21832199
}
2200+
} else {
2201+
// head is shorter so:
2202+
// 1. copy head backwards
2203+
// 2. rotate used part of the buffer
2204+
// 3. update head to point to the new beginning (which is the beginning of the buffer)
21842205

2185-
self.head = 0;
2206+
unsafe {
2207+
// if there is no free space in the buffer, then the slices are already
2208+
// right next to each other and we don't need to move any memory.
2209+
if free != 0 {
2210+
// copy the head slice to lie right behind the tail slice.
2211+
self.copy(self.head, tail_len, head_len);
2212+
}
2213+
2214+
// because we copied the head slice so that both slices lie right
2215+
// next to each other, all the elements in the range are initialized.
2216+
let slice = &mut *self.buffer_range(0..self.len);
2217+
2218+
// because the deque wasn't contiguous, we know that `head_len < self.len == slice.len()`
2219+
// so this will never panic.
2220+
slice.rotate_right(head_len);
2221+
2222+
// the used part of the buffer now is `0..self.len`, so set
2223+
// `head` to the beginning of that range.
2224+
self.head = 0;
2225+
}
21862226
}
21872227
}
21882228

‎library/alloc/src/collections/vec_deque/tests.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ fn test_binary_search_key() {
465465
}
466466

467467
#[test]
468-
fn make_contiguous_big_tail() {
468+
fn make_contiguous_big_head() {
469469
let mut tester = VecDeque::with_capacity(15);
470470

471471
for i in 0..3 {
@@ -480,14 +480,14 @@ fn make_contiguous_big_tail() {
480480
assert_eq!(tester.capacity(), 15);
481481
assert_eq!((&[9, 8, 7, 6, 5, 4, 3] as &[_], &[0, 1, 2] as &[_]), tester.as_slices());
482482

483-
let expected_start = tester.to_physical_idx(tester.len);
483+
let expected_start = tester.as_slices().1.len();
484484
tester.make_contiguous();
485485
assert_eq!(tester.head, expected_start);
486486
assert_eq!((&[9, 8, 7, 6, 5, 4, 3, 0, 1, 2] as &[_], &[] as &[_]), tester.as_slices());
487487
}
488488

489489
#[test]
490-
fn make_contiguous_big_head() {
490+
fn make_contiguous_big_tail() {
491491
let mut tester = VecDeque::with_capacity(15);
492492

493493
for i in 0..8 {
@@ -507,13 +507,13 @@ fn make_contiguous_big_head() {
507507

508508
#[test]
509509
fn make_contiguous_small_free() {
510-
let mut tester = VecDeque::with_capacity(15);
510+
let mut tester = VecDeque::with_capacity(16);
511511

512-
for i in 'A' as u8..'I' as u8 {
512+
for i in b'A'..b'I' {
513513
tester.push_back(i as char);
514514
}
515515

516-
for i in 'I' as u8..'N' as u8 {
516+
for i in b'I'..b'N' {
517517
tester.push_front(i as char);
518518
}
519519

@@ -529,16 +529,16 @@ fn make_contiguous_small_free() {
529529
);
530530

531531
tester.clear();
532-
for i in 'I' as u8..'N' as u8 {
532+
for i in b'I'..b'N' {
533533
tester.push_back(i as char);
534534
}
535535

536-
for i in 'A' as u8..'I' as u8 {
536+
for i in b'A'..b'I' {
537537
tester.push_front(i as char);
538538
}
539539

540540
// IJKLM...HGFEDCBA
541-
let expected_start = 0;
541+
let expected_start = 3;
542542
tester.make_contiguous();
543543
assert_eq!(tester.head, expected_start);
544544
assert_eq!(

0 commit comments

Comments
 (0)
Please sign in to comment.