1// Copyright 2016 Amanieu d'Antras
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::thread_parker;
9use core::hint::spin_loop;
10
11// Wastes some CPU time for the given number of iterations,
12// using a hint to indicate to the CPU that we are spinning.
13#[inline]
14fn cpu_relax(iterations: u32) {
15 for _ in 0..iterations {
16 spin_loop()
17 }
18}
19
20/// A counter used to perform exponential backoff in spin loops.
21#[derive(Default)]
22pub struct SpinWait {
23 counter: u32,
24}
25
26impl SpinWait {
27 /// Creates a new `SpinWait`.
28 #[inline]
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 /// Resets a `SpinWait` to its initial state.
34 #[inline]
35 pub fn reset(&mut self) {
36 self.counter = 0;
37 }
38
39 /// Spins until the sleep threshold has been reached.
40 ///
41 /// This function returns whether the sleep threshold has been reached, at
42 /// which point further spinning has diminishing returns and the thread
43 /// should be parked instead.
44 ///
45 /// The spin strategy will initially use a CPU-bound loop but will fall back
46 /// to yielding the CPU to the OS after a few iterations.
47 #[inline]
48 pub fn spin(&mut self) -> bool {
49 if self.counter >= 10 {
50 return false;
51 }
52 self.counter += 1;
53 if self.counter <= 3 {
54 cpu_relax(1 << self.counter);
55 } else {
56 thread_parker::thread_yield();
57 }
58 true
59 }
60
61 /// Spins without yielding the thread to the OS.
62 ///
63 /// Instead, the backoff is simply capped at a maximum value. This can be
64 /// used to improve throughput in `compare_exchange` loops that have high
65 /// contention.
66 #[inline]
67 pub fn spin_no_yield(&mut self) {
68 self.counter += 1;
69 if self.counter > 10 {
70 self.counter = 10;
71 }
72 cpu_relax(1 << self.counter);
73 }
74}
75