Skip to main content

a/sort/
bubble.rs

1use core::hint::select_unpredictable;
2use core::mem::{self, ManuallyDrop};
3use core::ptr;
4
5/// Sorts a slice in place using bubble sort.
6///
7/// Each pass walks the unsorted prefix from left to right, carrying the largest element
8/// seen so far towards the end until it meets an element that is not smaller than it.
9/// After a pass, everything from the position of the last move onwards is in its final
10/// place, so the next pass stops there; a pass that moves nothing ends the sort.
11///
12/// Four refinements keep the algorithm as cheap as bubble sort can be:
13///
14/// * **Last-move tracking.** The next pass scans only up to the last element that moved,
15///   so already-sorted input finishes after a single pass of `n - 1` comparisons and
16///   partially sorted tails are never rescanned.
17/// * **Hole shifting instead of swapping.** An element travelling `k` slots to the right is
18///   read once, the `k` smaller elements are each shifted left by one, and the element is
19///   written once into its final slot: `k + 1` moves instead of the `3k` moves of `k` swaps.
20/// * **Branchless passes for small elements.** Whether a comparison starts or ends a carry is
21///   hard to predict on most inputs, and each misprediction costs as much as a dozen
22///   comparisons. For elements of at most 8 bytes with no drop glue (integers, `char`,
23///   `bool`, references), a pass can instead hold the running maximum in a register and, at
24///   every comparison, write the smaller of it and the next element into the slot behind,
25///   with no branch on the outcome. The comparisons and their outcomes are the same; an
26///   element that is not overtaken is merely written back to its own slot.
27/// * **Two passes at once.** Each branchless step waits for the one before it, so a single
28///   pass runs at the latency of a comparison. Pass `k + 1` only reads slots that pass `k`
29///   has finished with, so it can trail pass `k` through the slice in the same loop: two
30///   independent chains of comparisons, each made exactly as if the passes ran one after
31///   the other; on random `u64` input up to 1.3 times the speed of one branchless pass.
32///
33/// Branchless passes cost more than predictable branches, so the sort picks the kind of each
34/// pass from the previous one: a branchy pass that turns out to carry often gives way to
35/// branchless ones from where it stands, and branchless passes that move few elements hand
36/// back to branchy ones. Already sorted input is done by one branchy pass, as before.
37/// On random `u64` and `u32` input this sorts 1.2–2.9 times faster than branching on every
38/// comparison (most around a hundred elements), and input whose elements are each a few slots
39/// from their places up to 3.3 times faster.
40///
41/// The number of comparisons is exactly that of classic bubble sort with early exit, and so
42/// is every outcome; only the order in which two consecutive passes make them is interleaved.
43/// Zero-sized elements, which are all alike, are left as they are without comparing them.
44///
45/// # Properties
46///
47/// * **Stable**: equal elements keep their relative order (only strictly smaller elements
48///   are overtaken).
49/// * **In place**: `O(1)` auxiliary memory, no allocation.
50/// * **Adaptive**: the cost grows with how far elements are from their sorted positions.
51///
52/// | Case                      | Comparisons | Moves  |
53/// |---------------------------|-------------|--------|
54/// | Best (already sorted)     | `O(n)`      | `0`    |
55/// | Average                   | `O(n²)`     | `O(n²)`|
56/// | Worst (reverse sorted)    | `O(n²)`     | `O(n²)`|
57///
58/// Bubble sort is quadratic and is mainly of educational value; prefer
59/// [`slice::sort`] for real workloads.
60///
61/// # Panics
62///
63/// Does not panic by itself. If the [`Ord`] implementation of `T` panics, the panic is
64/// propagated and `arr` is left in an unspecified order, but it still holds every original
65/// element exactly once: nothing is dropped twice or leaked.
66///
67/// # Examples
68///
69/// ```
70/// use a::sort::bubble;
71///
72/// let mut v = [5, 1, 4, 2, 8, 0];
73/// bubble(&mut v);
74/// assert_eq!(v, [0, 1, 2, 4, 5, 8]);
75///
76/// let mut words = vec!["pear", "apple", "fig"];
77/// bubble(&mut words);
78/// assert_eq!(words, ["apple", "fig", "pear"]);
79/// ```
80pub fn bubble<T: Ord>(arr: &mut [T]) {
81    if mem::size_of::<T>() == 0 {
82        // Zero-sized elements are all alike: there is nothing to reorder.
83        return;
84    }
85    let base = arr.as_mut_ptr();
86    // Everything at index `n` and beyond is already in its final position.
87    let mut n = arr.len();
88    // SAFETY: `n` starts at `arr.len()` and only shrinks, so every pass works inside `arr`.
89    unsafe {
90        if !branchless_pays::<T>() {
91            while n > 1 {
92                let Ok(pass) = forward::<T, false>(base, n, 0, 0) else {
93                    unreachable!("a pass that never gives up gave up")
94                };
95                n = pass.last;
96            }
97            return;
98        }
99        let mut branchy = true;
100        while n > 1 {
101            let (j0, last) = if branchy {
102                match forward::<T, true>(base, n, BRANCH_MOVE_COST, BRANCH_CARRY_COST) {
103                    Ok(pass) => {
104                        branchy = BRANCH_MOVE_COST * pass.moves + BRANCH_CARRY_COST * pass.carries
105                            < n - 1;
106                        n = pass.last;
107                        continue;
108                    }
109                    Err(resume) => resume,
110                }
111            } else {
112                (0, 0)
113            };
114            let pair = two_passes(base, n, j0, last);
115            // A branchless pass does not see its carries; it makes at most `min(moves,
116            // comparisons - moves)` of them.
117            let carries = pair.moves.min(pair.comparisons - pair.moves);
118            branchy =
119                BRANCH_MOVE_COST * pair.moves + BRANCH_CARRY_COST * carries < pair.comparisons;
120            n = pair.last;
121        }
122    }
123}
124
125/// Whether passes over elements of type `T` may run without branching on comparisons.
126///
127/// Measured on the whole sort on random input: 1.2–2.9 times faster for `u32` and `u64`;
128/// for `(u64, u64)`, `Box<str>` and `String` up to 2.3 times slower (each step waits for a
129/// longer comparison), and for `Box<u64>` 1.5–2.6 times slower (each comparison first loads
130/// what the held element points to). Owning pointers are kept out by their drop glue; plain
131/// references cannot be told apart from integers, take the branchless path, and pay that
132/// load in the same way.
133pub(super) const fn branchless_pays<T>() -> bool {
134    mem::size_of::<T>() <= 8 && !mem::needs_drop::<T>()
135}
136
137/// A branchy pass gives way to branchless ones once
138/// `BRANCH_MOVE_COST · moves + BRANCH_CARRY_COST · carries` reaches the comparisons made: a
139/// carry costs up to two mispredictions, while a move inside a predicted carry costs about
140/// as much as a branchless step. Tuned on `u64` and `u32` from 16 to 5,000 elements.
141const BRANCH_MOVE_COST: usize = 2;
142const BRANCH_CARRY_COST: usize = 8;
143
144/// A branchy pass checks whether to give up only after this many elements, so that a single
145/// early carry does not decide for a whole pass.
146pub(super) const GIVE_UP_AFTER: usize = 32;
147
148/// Whether a branchy pass looks for its next carry in a loop of its own.
149///
150/// That loop compiles to one taken branch per element, while the scan and the carry in one
151/// loop compiled to two for `u64` in the adaptive passes, which made a scan twice as slow;
152/// elements of more than 32 bytes gain 1.1–1.8 times on nearly sorted input. Elements of 9
153/// to 32 bytes, and those with drop glue, keep the single loop: with it, random `String`s
154/// sort up to 1.2 times faster than with the separate one.
155pub(super) const fn scan_apart<T, const GIVE_UP: bool>() -> bool {
156    GIVE_UP || mem::size_of::<T>() > 32
157}
158
159/// What a complete branchy pass found.
160pub(super) struct Pass {
161    /// The final index of the last element moved (`0` if none was): everything from there
162    /// onwards is in its final position.
163    pub(super) last: usize,
164    /// The number of carries (elements picked up and moved at least one slot).
165    pub(super) carries: usize,
166    /// The number of elements overtaken.
167    pub(super) moves: usize,
168}
169
170/// One forward pass over `base[..n]`, `n >= 2`, branching on each comparison.
171///
172/// With `GIVE_UP`, the pass stops at the end of a carry once it has scanned at least
173/// [`GIVE_UP_AFTER`] elements and `move_cost · moves + carry_cost · carries` has reached the
174/// number of elements scanned, that is, once branches look expensive. It
175/// then returns `Err((j0, last))`: `base[j0]` is the running maximum, `base[..j0]` holds what
176/// the pass has finished with, and `last` is the final index of its last move so far, so that
177/// a branchless pass can take over from `j0`.
178///
179/// # Safety
180///
181/// `base[..n]` must be valid for reads and writes and hold initialised elements.
182#[inline(always)]
183pub(super) unsafe fn forward<T: Ord, const GIVE_UP: bool>(
184    base: *mut T,
185    n: usize,
186    move_cost: usize,
187    carry_cost: usize,
188) -> Result<Pass, (usize, usize)> {
189    // SAFETY: `p` walks `base[1..n]`, so every pointer stays inside `base[..n]`;
190    // `carry_right` keeps the slice whole.
191    unsafe {
192        let end = base.add(n);
193        let mut last = base;
194        let (mut carries, mut moves) = (0, 0);
195        let mut p = base.add(1);
196        // What a carry ending at `p` leaves to do: record it, stop at the end, and maybe give
197        // up.
198        macro_rules! after_carry {
199            ($from:expr) => {
200                last = p.sub(1);
201                if GIVE_UP {
202                    carries += 1;
203                    moves += p.offset_from_unsigned($from);
204                }
205                if p == end {
206                    break;
207                }
208                if GIVE_UP {
209                    let seen = p.offset_from_unsigned(base);
210                    if seen >= GIVE_UP_AFTER && move_cost * moves + carry_cost * carries >= seen {
211                        return Err((seen, last.offset_from_unsigned(base)));
212                    }
213                }
214            };
215        }
216        if scan_apart::<T, GIVE_UP>() {
217            loop {
218                while p < end && !(*p < *p.sub(1)) {
219                    p = p.add(1);
220                }
221                if p == end {
222                    break;
223                }
224                let from = p;
225                p = carry_right(p, end);
226                after_carry!(from);
227                p = p.add(1);
228            }
229        } else {
230            while p < end {
231                if *p < *p.sub(1) {
232                    let from = p;
233                    p = carry_right(p, end);
234                    after_carry!(from);
235                }
236                p = p.add(1);
237            }
238        }
239        Ok(Pass {
240            last: last.offset_from_unsigned(base),
241            carries,
242            moves,
243        })
244    }
245}
246
247/// Carries `*p.sub(1)`, the largest element seen in a pass, right past every strictly
248/// smaller element from `p` on, shifting each of those left by one slot. Returns the position
249/// after the carried element's final slot: `end`, or the first element not smaller than it.
250///
251/// # Safety
252///
253/// `base[..end]` (for some `base < p`) must be valid for reads and writes and hold
254/// initialised elements, `p < end`, and `*p < *p.sub(1)`.
255#[inline(always)]
256unsafe fn carry_right<T: Ord>(mut p: *mut T, end: *mut T) -> *mut T {
257    // SAFETY: `p` stays below `end` whenever it is read. The element at `p - 1` is moved into
258    // `hole`, which leaves exactly one slot logically uninitialised: `hole.dst`. Each step
259    // moves the next element into that slot and advances `hole.dst` onto the one just
260    // vacated, so source and destination never overlap. When `hole` is dropped — normally, or
261    // while unwinding from a panicking comparison — it writes the carried element back into
262    // `hole.dst`, so the slice again holds every element exactly once.
263    unsafe {
264        let mut hole = Hole {
265            value: ManuallyDrop::new(ptr::read(p.sub(1))),
266            dst: p.sub(1),
267        };
268        loop {
269            ptr::copy_nonoverlapping(p, hole.dst, 1);
270            hole.dst = p;
271            p = p.add(1);
272            if p == end || *p >= *hole.value {
273                return p;
274            }
275        }
276    }
277}
278
279/// What a pair of branchless passes found about the second (trailing) one.
280struct Pair {
281    /// The final index of the trailing pass's last move (`0` if none).
282    last: usize,
283    /// The number of comparisons the trailing pass made.
284    comparisons: usize,
285    /// The number of elements the trailing pass overtook.
286    moves: usize,
287}
288
289/// Runs the pass that is under way over `base[..lim]` — its running maximum at `j0`,
290/// everything before `j0` finished, `last_l` the final index of its last move so far — to the
291/// end, and the next pass over `base[..last of the first]` trailing it, both branchless.
292///
293/// The trailing pass reads `base[k]` only once `k` is below the leading pass's last move so
294/// far, which is never more than the slot the leading pass holds vacant: it only sees slots
295/// the leading pass has finished with, and it would scan them anyway, since the leading pass's
296/// last move can only grow. So it makes exactly the comparisons it would make after the
297/// leading pass, with the same outcomes.
298///
299/// # Safety
300///
301/// `base[..lim]` must be valid for reads and writes and hold initialised elements, `lim >= 2`,
302/// `j0 < lim`, and if `j0 > 0`, `last_l < j0`.
303#[inline(always)]
304unsafe fn two_passes<T: Ord>(base: *mut T, lim: usize, j0: usize, mut last_l: usize) -> Pair {
305    // SAFETY: every index is `< lim`. Each pass holds its running maximum outside the slice,
306    // in `held_l` or `held_t`, which leaves one slot per pass logically uninitialised: the
307    // `dst` of its guard. The leading pass's vacant slot is at the index it has just
308    // processed; the trailing pass's is below `k`, which is below `last_l`, which is at most
309    // the leading pass's vacant slot, so the two are distinct and neither pass reads the
310    // other's. When a guard is dropped — normally, or while unwinding from a panicking
311    // comparison — it writes its held element into its vacant slot, so the slice again holds
312    // every element exactly once. The held elements are accessed only through `hl` and `ht`
313    // once those exist, so the guards' `src` pointers stay valid.
314    unsafe {
315        let mut held_l = ManuallyDrop::new(ptr::read(base.add(j0)));
316        let hl: *mut ManuallyDrop<T> = &mut held_l;
317        let mut gl = Held {
318            src: hl.cast::<T>(),
319            dst: base.add(j0),
320        };
321        let mut j = j0 + 1;
322        if j0 == 0 {
323            // Finish slot 0 before the trailing pass picks it up.
324            if step(hl, &mut gl.dst, base.add(1)) {
325                last_l = 1;
326            }
327            j = 2;
328        }
329        let mut held_t = ManuallyDrop::new(ptr::read(base));
330        let ht: *mut ManuallyDrop<T> = &mut held_t;
331        let mut gt = Held {
332            src: ht.cast::<T>(),
333            dst: base,
334        };
335        let (mut last_t, mut moves_t) = (0, 0);
336        let mut k = 1;
337        while j < lim {
338            let moved = step(hl, &mut gl.dst, base.add(j));
339            last_l = select_unpredictable(moved, j, last_l);
340            j += 1;
341            if k < last_l {
342                let moved = step(ht, &mut gt.dst, base.add(k));
343                last_t = select_unpredictable(moved, k, last_t);
344                moves_t += usize::from(moved);
345                k += 1;
346            }
347        }
348        // The leading pass is done: its held element goes into its final slot, `lim - 1`, and
349        // the trailing pass finishes alone.
350        drop(gl);
351        while k < last_l {
352            let moved = step(ht, &mut gt.dst, base.add(k));
353            last_t = select_unpredictable(moved, k, last_t);
354            moves_t += usize::from(moved);
355            k += 1;
356        }
357        drop(gt);
358        Pair {
359            last: last_t,
360            comparisons: k - 1,
361            moves: moves_t,
362        }
363    }
364}
365
366/// One branchless step of a pass: compares the element at `x` with the held one, writes the
367/// smaller into the vacant slot `*hole` (the held one if they are equal, which keeps the sort
368/// stable), keeps the larger held, and makes `x` the vacant slot. Returns whether the element
369/// at `x` was smaller, that is, overtaken.
370///
371/// # Safety
372///
373/// `held` must point to an initialised element that belongs in the vacant slot `*hole`, and
374/// `x` to an initialised element of the slice other than `*hole`; afterwards the held element
375/// belongs in `x`. The comparison is the only call that can panic, and it runs before
376/// anything is moved.
377#[inline(always)]
378pub(super) unsafe fn step<T: Ord>(
379    held: *mut ManuallyDrop<T>,
380    hole: &mut *mut T,
381    x: *mut T,
382) -> bool {
383    // SAFETY: as documented above; each of the two elements is written exactly once, one into
384    // the vacant slot and one into `held`, from bitwise copies that are never dropped.
385    unsafe {
386        let smaller = *x < **held;
387        let stays = select_unpredictable(smaller, ManuallyDrop::new(ptr::read(x)), ptr::read(held));
388        let goes_on =
389            select_unpredictable(smaller, ptr::read(held), ManuallyDrop::new(ptr::read(x)));
390        ptr::write(*hole, ManuallyDrop::into_inner(stays));
391        ptr::write(held, goes_on);
392        *hole = x;
393        smaller
394    }
395}
396
397/// An element temporarily moved out of a slice, plus the slot it will be written back to.
398///
399/// Writing back on drop is what keeps [`bubble`] panic-safe: the slice is whole again even
400/// if a comparison unwinds while the element is out.
401pub(super) struct Hole<T> {
402    pub(super) value: ManuallyDrop<T>,
403    pub(super) dst: *mut T,
404}
405
406impl<T> Drop for Hole<T> {
407    fn drop(&mut self) {
408        // SAFETY: `dst` points to the single uninitialised slot of the slice being sorted,
409        // and `value` is never used again after this write.
410        unsafe { ptr::copy_nonoverlapping(&*self.value, self.dst, 1) }
411    }
412}
413
414/// A vacant slot of a slice, plus the element held outside the slice that belongs there.
415///
416/// Writing the held element back on drop is what keeps the branchless passes panic-safe.
417pub(super) struct Held<T> {
418    pub(super) src: *const T,
419    pub(super) dst: *mut T,
420}
421
422impl<T> Drop for Held<T> {
423    fn drop(&mut self) {
424        // SAFETY: `dst` points to a vacant slot of the slice being sorted, and `src` to the
425        // held element, which is never used again after this write.
426        unsafe { ptr::copy_nonoverlapping(self.src, self.dst, 1) }
427    }
428}