use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use mnemosyne_core::constants::NUM_SIZE_CLASSES;
pub(super) static ALLOC_COUNT: [AtomicU64; NUM_SIZE_CLASSES] =
[const { AtomicU64::new(0) }; NUM_SIZE_CLASSES];
pub(super) static DEALLOC_COUNT: [AtomicU64; NUM_SIZE_CLASSES] =
[const { AtomicU64::new(0) }; NUM_SIZE_CLASSES];
pub(super) static REQUESTED_BYTES: [AtomicU64; NUM_SIZE_CLASSES] =
[const { AtomicU64::new(0) }; NUM_SIZE_CLASSES];
pub(super) static RESET_GENERATION: AtomicU32 = AtomicU32::new(0);
const PENDING_SLOTS: usize = 8;
pub(super) const FLUSH_BATCH: u32 = 64;
const EMPTY_CLASS: usize = usize::MAX;
const _: () = assert!(PENDING_SLOTS.is_power_of_two());
#[derive(Clone, Copy)]
pub(super) struct PendingCount {
pub(super) class: usize,
pub(super) count: u32,
generation: u32,
}
impl PendingCount {
pub(super) const fn new() -> Self {
Self {
class: EMPTY_CLASS,
count: 0,
generation: 0,
}
}
#[inline(always)]
pub(super) fn record(&mut self, class: usize, global: &[AtomicU64; NUM_SIZE_CLASSES]) {
if self.class != class {
self.flush(global);
self.class = class;
self.generation = RESET_GENERATION.load(Ordering::Relaxed);
}
self.count += 1;
if self.count == FLUSH_BATCH {
self.flush(global);
}
}
#[inline]
pub(super) fn flush(&mut self, global: &[AtomicU64; NUM_SIZE_CLASSES]) {
if self.count != 0 {
let current_gen = RESET_GENERATION.load(Ordering::Relaxed);
if current_gen == self.generation && self.class < NUM_SIZE_CLASSES {
global[self.class].fetch_add(self.count as u64, Ordering::Relaxed);
}
self.count = 0;
self.class = EMPTY_CLASS;
}
}
}
struct ThreadBinStats {
alloc: [PendingCount; PENDING_SLOTS],
dealloc: [PendingCount; PENDING_SLOTS],
}
impl ThreadBinStats {
const fn new() -> Self {
Self {
alloc: [PendingCount::new(); PENDING_SLOTS],
dealloc: [PendingCount::new(); PENDING_SLOTS],
}
}
#[inline(always)]
fn record_alloc(&mut self, class: usize) {
self.alloc[class & (PENDING_SLOTS - 1)].record(class, &ALLOC_COUNT);
}
#[inline(always)]
fn record_dealloc(&mut self, class: usize) {
self.dealloc[class & (PENDING_SLOTS - 1)].record(class, &DEALLOC_COUNT);
}
#[inline]
fn flush(&mut self) {
for pending in &mut self.alloc {
pending.flush(&ALLOC_COUNT);
}
for pending in &mut self.dealloc {
pending.flush(&DEALLOC_COUNT);
}
}
}
impl Drop for ThreadBinStats {
fn drop(&mut self) {
self.flush();
}
}
std::thread_local! {
static THREAD_STATS: core::cell::UnsafeCell<ThreadBinStats> =
const { core::cell::UnsafeCell::new(ThreadBinStats::new()) };
}
#[inline]
pub(super) fn allocation_bytes(alloc_count: u64, block_size: usize) -> u64 {
alloc_count.saturating_mul(block_size as u64)
}
#[inline(always)]
pub(crate) fn record_alloc_with_size(class: usize, adjusted_size: usize) {
if class < NUM_SIZE_CLASSES {
THREAD_STATS.with(|stats| {
unsafe { (*stats.get()).record_alloc(class) };
});
REQUESTED_BYTES[class].fetch_add(adjusted_size as u64, Ordering::Relaxed);
}
}
#[inline(always)]
pub(crate) fn record_dealloc(class: usize) {
if class < NUM_SIZE_CLASSES {
THREAD_STATS.with(|stats| {
unsafe { (*stats.get()).record_dealloc(class) };
});
}
}
#[inline]
pub(super) fn flush_current_thread() {
THREAD_STATS.with(|stats| {
unsafe { (*stats.get()).flush() };
});
}