use core::cell::Cell;
use core::fmt;
use std::sync::OnceLock;
use std::{hint, thread};
static ADAPTIVE_BACKOFF: OnceLock<usize> = OnceLock::new();
pub struct Backoff {
step: Cell<usize>,
spin_limit: usize,
yield_limit: usize,
}
impl Backoff {
pub(crate) fn new() -> Self {
let adaptive = *ADAPTIVE_BACKOFF.get_or_init(|| {
thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(4)
});
let spin_limit = match adaptive {
1..=2 => 6,
3..=8 => 8,
9..=32 => 10,
_ => 12,
};
let yield_limit = match adaptive {
1..=2 => 2,
3..=8 => 4,
9..=32 => 6,
_ => 10,
} + spin_limit;
Backoff {
step: Cell::new(0),
spin_limit,
yield_limit,
}
}
#[inline]
pub(crate) fn reset(&self) {
self.step.set(0);
}
#[inline]
pub(crate) fn spin(&self) {
let spins = self.step.get();
for _ in 0..1 << spins.min(self.spin_limit) {
hint::spin_loop();
}
if spins <= self.spin_limit {
self.step.set(spins + 1);
}
}
#[inline]
pub(crate) fn yielder(&self) {
self.step.set(self.spin_limit + 1);
}
#[inline]
pub(crate) fn snooze(&self) {
let spins = self.step.get();
if spins <= self.spin_limit {
for _ in 0..1 << spins {
hint::spin_loop();
}
} else {
thread::yield_now();
}
if spins <= self.yield_limit {
self.step.set(spins + 1);
}
}
#[inline]
pub(crate) fn is_completed(&self) -> bool {
self.step.get() > self.yield_limit
}
#[inline]
pub(crate) fn is_yielding(&self) -> bool {
self.step.get() >= self.spin_limit
}
#[inline]
pub(crate) fn cpu_relax(mut counter: u32) {
if counter >= 10 {
return;
}
counter += 1;
if counter <= 3 {
for _ in 0..(1 << counter) {
hint::spin_loop();
}
} else {
thread::yield_now();
}
}
}
impl fmt::Debug for Backoff {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Backoff")
.field("step", &self.step)
.field("is_completed", &self.is_completed())
.finish()
}
}
impl Default for Backoff {
fn default() -> Backoff {
Backoff::new()
}
}