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}