use core::alloc::{GlobalAlloc, Layout};
use core::cell::Cell;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct AllocationDelta {
pub allocations: usize,
pub deallocations: usize,
pub reallocations: usize,
pub bytes_allocated: usize,
pub bytes_deallocated: usize,
}
impl AllocationDelta {
#[must_use]
pub const fn bytes_retained(&self) -> isize {
self.bytes_allocated
.wrapping_sub(self.bytes_deallocated)
.cast_signed()
}
const fn since(&self, earlier: &Self) -> Self {
Self {
allocations: self.allocations.wrapping_sub(earlier.allocations),
deallocations: self.deallocations.wrapping_sub(earlier.deallocations),
reallocations: self.reallocations.wrapping_sub(earlier.reallocations),
bytes_allocated: self.bytes_allocated.wrapping_sub(earlier.bytes_allocated),
bytes_deallocated: self
.bytes_deallocated
.wrapping_sub(earlier.bytes_deallocated),
}
}
}
struct Counters {
allocations: Cell<usize>,
deallocations: Cell<usize>,
reallocations: Cell<usize>,
bytes_allocated: Cell<usize>,
bytes_deallocated: Cell<usize>,
}
impl Counters {
const fn new() -> Self {
Self {
allocations: Cell::new(0),
deallocations: Cell::new(0),
reallocations: Cell::new(0),
bytes_allocated: Cell::new(0),
bytes_deallocated: Cell::new(0),
}
}
fn totals(&self) -> AllocationDelta {
AllocationDelta {
allocations: self.allocations.get(),
deallocations: self.deallocations.get(),
reallocations: self.reallocations.get(),
bytes_allocated: self.bytes_allocated.get(),
bytes_deallocated: self.bytes_deallocated.get(),
}
}
}
std::thread_local! {
static COUNTERS: Counters = const { Counters::new() };
}
fn bump(cell: &Cell<usize>, by: usize) {
cell.set(cell.get().wrapping_add(by));
}
fn record_alloc(size: usize) {
COUNTERS.with(|counters| {
bump(&counters.allocations, 1);
bump(&counters.bytes_allocated, size);
});
}
fn record_dealloc(size: usize) {
COUNTERS.with(|counters| {
bump(&counters.deallocations, 1);
bump(&counters.bytes_deallocated, size);
});
}
fn record_realloc(old_size: usize, new_size: usize) {
COUNTERS.with(|counters| {
bump(&counters.reallocations, 1);
if new_size >= old_size {
bump(&counters.bytes_allocated, new_size - old_size);
} else {
bump(&counters.bytes_deallocated, old_size - new_size);
}
});
}
#[derive(Debug, Default)]
pub struct CountingAllocator<A> {
inner: A,
}
impl<A> CountingAllocator<A> {
#[must_use]
pub const fn new(inner: A) -> Self {
Self { inner }
}
}
unsafe impl<A: GlobalAlloc> GlobalAlloc for CountingAllocator<A> {
#[inline]
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { self.inner.alloc(layout) };
if !ptr.is_null() {
record_alloc(layout.size());
}
ptr
}
#[inline]
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { self.inner.dealloc(ptr, layout) };
record_dealloc(layout.size());
}
#[inline]
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { self.inner.alloc_zeroed(layout) };
if !ptr.is_null() {
record_alloc(layout.size());
}
ptr
}
#[inline]
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_ptr = unsafe { self.inner.realloc(ptr, layout, new_size) };
if !new_ptr.is_null() {
record_realloc(layout.size(), new_size);
}
new_ptr
}
}
pub fn measure<R>(body: impl FnOnce() -> R) -> (R, AllocationDelta) {
let before = COUNTERS.with(Counters::totals);
let value = body();
let after = COUNTERS.with(Counters::totals);
(value, after.since(&before))
}