use crossbeam_utils::CachePadded;
use num_traits::Num;
use std::sync::atomic::{AtomicIsize, Ordering};
pub trait Counter: 'static {
type Primitive: Num;
const ZERO: Self;
fn add_assign(&self, n: Self::Primitive);
fn sub_assign(&self, n: Self::Primitive);
fn fetch(&self) -> Self::Primitive;
}
#[repr(transparent)]
pub struct RelaxedCounter {
counter: CachePadded<AtomicIsize>,
}
impl Counter for RelaxedCounter {
type Primitive = isize;
const ZERO: Self = Self {
counter: CachePadded::new(AtomicIsize::new(0)),
};
#[inline(always)]
fn add_assign(&self, n: isize) {
let _ = self.counter.fetch_add(n, Ordering::Relaxed);
}
#[inline(always)]
fn sub_assign(&self, n: isize) {
let _ = self.counter.fetch_sub(n, Ordering::Relaxed);
}
#[inline(always)]
fn fetch(&self) -> isize {
self.counter.load(Ordering::Relaxed)
}
}
pub struct DistributedCounter<const BUCKETS: usize> {
counters: [CachePadded<AtomicIsize>; BUCKETS],
}
impl<const BUCKETS: usize> DistributedCounter<BUCKETS> {
const fn new() -> Self {
const BUCKET: CachePadded<AtomicIsize> = CachePadded::new(AtomicIsize::new(0));
Self {
counters: [BUCKET; BUCKETS],
}
}
fn thread_id() -> usize {
use std::sync::atomic::AtomicUsize;
static THREADS: AtomicUsize = AtomicUsize::new(0);
thread_local! {
pub static ID: usize = THREADS.fetch_add(1, Ordering::SeqCst);
}
ID.try_with(|id| *id).unwrap_or(0)
}
#[inline(always)]
fn try_add_assign(bucket: &AtomicIsize, n: isize) -> Result<isize, isize> {
let count = bucket.load(Ordering::SeqCst);
bucket.compare_exchange(
count,
count.wrapping_add(n),
Ordering::SeqCst,
Ordering::SeqCst,
)
}
#[inline(always)]
fn add_assign(&self, n: isize) {
let id = Self::thread_id();
let mut bucket = id % BUCKETS;
loop {
if Self::try_add_assign(&self.counters[bucket], n).is_ok() {
return;
} else {
bucket = bucket.wrapping_add(1) % BUCKETS;
}
}
}
}
impl<const BUCKETS: usize> Counter for DistributedCounter<BUCKETS> {
type Primitive = isize;
const ZERO: Self = Self::new();
fn add_assign(&self, n: isize) {
self.add_assign(n)
}
fn sub_assign(&self, n: isize) {
self.add_assign(-n)
}
fn fetch(&self) -> isize {
let mut sum = 0isize;
for counter in &self.counters {
sum = sum.wrapping_add(counter.load(Ordering::SeqCst));
}
sum
}
}