use std::alloc::{GlobalAlloc, Layout, System};
pub struct CountingAllocator;
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc(layout) };
if !ptr.is_null() {
counters::allocated(layout.size());
}
ptr
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
let ptr = unsafe { System.alloc_zeroed(layout) };
if !ptr.is_null() {
counters::allocated(layout.size());
}
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
counters::deallocated(layout.size());
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
if !new_ptr.is_null() {
counters::reallocated(layout.size(), new_size);
}
new_ptr
}
}
#[cfg(feature = "render-stats")]
pub(crate) fn allocations() -> u64 {
counters::allocations()
}
#[cfg(feature = "render-stats")]
pub(crate) fn bytes_allocated() -> u64 {
counters::bytes_allocated()
}
pub struct Region {
allocations: u64,
bytes: u64,
live: usize,
}
impl Region {
pub fn start() -> Self {
Self {
allocations: counters::allocations(),
bytes: counters::bytes_allocated(),
live: counters::restart_peak(),
}
}
pub fn allocations(&self) -> u64 {
counters::allocations() - self.allocations
}
pub fn bytes(&self) -> u64 {
counters::bytes_allocated() - self.bytes
}
pub fn peak_live_bytes(&self) -> usize {
counters::peak_live_bytes().saturating_sub(self.live)
}
pub fn retained_bytes(&self) -> i64 {
counters::live_bytes() as i64 - self.live as i64
}
}
mod counters {
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering::Relaxed};
struct Counters {
allocations: AtomicU64,
bytes: AtomicU64,
live: AtomicUsize,
peak: AtomicUsize,
}
static COUNTERS: Counters = Counters::new();
impl Counters {
const fn new() -> Self {
Self {
allocations: AtomicU64::new(0),
bytes: AtomicU64::new(0),
live: AtomicUsize::new(0),
peak: AtomicUsize::new(0),
}
}
fn restart_peak(&self) -> usize {
let live = self.live.load(Relaxed);
self.peak.store(live, Relaxed);
live
}
}
#[cfg(feature = "render-stats")]
impl Counters {
#[inline]
fn allocated(&self, size: usize) {
self.allocations.fetch_add(1, Relaxed);
self.bytes.fetch_add(size as u64, Relaxed);
self.grew(size);
}
#[inline]
fn deallocated(&self, size: usize) {
self.live.fetch_sub(size, Relaxed);
}
#[inline]
fn reallocated(&self, old_size: usize, new_size: usize) {
self.allocations.fetch_add(1, Relaxed);
match new_size.checked_sub(old_size) {
Some(grown) => {
self.bytes.fetch_add(grown as u64, Relaxed);
self.grew(grown);
}
None => {
self.live.fetch_sub(old_size - new_size, Relaxed);
}
}
}
#[inline]
fn grew(&self, by: usize) {
let live = self.live.fetch_add(by, Relaxed) + by;
self.peak.fetch_max(live, Relaxed);
}
}
#[cfg(feature = "render-stats")]
#[inline]
pub fn allocated(size: usize) {
COUNTERS.allocated(size);
}
#[cfg(feature = "render-stats")]
#[inline]
pub fn deallocated(size: usize) {
COUNTERS.deallocated(size);
}
#[cfg(feature = "render-stats")]
#[inline]
pub fn reallocated(old_size: usize, new_size: usize) {
COUNTERS.reallocated(old_size, new_size);
}
#[cfg(not(feature = "render-stats"))]
#[inline(always)]
pub fn allocated(_size: usize) {}
#[cfg(not(feature = "render-stats"))]
#[inline(always)]
pub fn deallocated(_size: usize) {}
#[cfg(not(feature = "render-stats"))]
#[inline(always)]
pub fn reallocated(_old_size: usize, _new_size: usize) {}
pub fn allocations() -> u64 {
COUNTERS.allocations.load(Relaxed)
}
pub fn bytes_allocated() -> u64 {
COUNTERS.bytes.load(Relaxed)
}
pub fn live_bytes() -> usize {
COUNTERS.live.load(Relaxed)
}
pub fn peak_live_bytes() -> usize {
COUNTERS.peak.load(Relaxed)
}
pub fn restart_peak() -> usize {
COUNTERS.restart_peak()
}
#[cfg(all(test, feature = "render-stats"))]
mod tests {
use super::*;
#[test]
fn an_allocation_is_one_request_and_its_bytes() {
let c = Counters::new();
c.allocated(64);
c.allocated(8);
assert_eq!(c.allocations.load(Relaxed), 2);
assert_eq!(c.bytes.load(Relaxed), 72);
assert_eq!(c.live.load(Relaxed), 72);
assert_eq!(c.peak.load(Relaxed), 72);
}
#[test]
fn freeing_lowers_the_live_bytes_and_nothing_else() {
let c = Counters::new();
c.allocated(64);
c.deallocated(64);
assert_eq!(c.allocations.load(Relaxed), 1);
assert_eq!(c.bytes.load(Relaxed), 64);
assert_eq!(c.live.load(Relaxed), 0);
assert_eq!(c.peak.load(Relaxed), 64, "the peak is a high-water mark");
}
#[test]
fn a_reallocation_that_grows_asks_for_the_difference() {
let c = Counters::new();
c.allocated(16);
c.reallocated(16, 1024);
assert_eq!(c.allocations.load(Relaxed), 2);
assert_eq!(c.bytes.load(Relaxed), 1024);
assert_eq!(c.live.load(Relaxed), 1024);
assert_eq!(c.peak.load(Relaxed), 1024);
}
#[test]
fn a_reallocation_that_shrinks_gives_bytes_back() {
let c = Counters::new();
c.allocated(1024);
c.reallocated(1024, 16);
assert_eq!(c.allocations.load(Relaxed), 2, "it is still a request");
assert_eq!(c.bytes.load(Relaxed), 1024, "and it asked for nothing");
assert_eq!(c.live.load(Relaxed), 16);
assert_eq!(c.peak.load(Relaxed), 1024);
}
#[test]
fn a_reallocation_to_the_same_size_moves_no_bytes() {
let c = Counters::new();
c.allocated(128);
c.reallocated(128, 128);
assert_eq!(c.bytes.load(Relaxed), 128);
assert_eq!(c.live.load(Relaxed), 128);
}
#[test]
fn restarting_the_peak_forgets_what_came_before() {
let c = Counters::new();
c.allocated(1024);
c.deallocated(1024);
c.allocated(16);
assert_eq!(c.restart_peak(), 16);
assert_eq!(c.peak.load(Relaxed), 16);
c.allocated(32);
assert_eq!(c.peak.load(Relaxed), 48);
}
}
}