bircni / get-size2

Determine the size in bytes an object occupies inside RAM.
MIT License
0 stars 0 forks source link

Arc<T> and similar don't account for bookkeeping information #4

Open hannoman opened 23 hours ago

hannoman commented 23 hours ago

I think the get_heap_size() implementation for Arc, Rc (and maybe similar types) is wrong because it doesn't account for the additional bookkeeping values these types allocate on the heap - which in these case are a strong and a weak reference count variable of size size_of::<usize>. Or put differently: Arc does not allocate T directly on the heap, but an ArcInner<T> which contains T and the refcounting variables. But the current implementation https://github.com/bircni/get-size2/blob/ce1477b8f7774c3163c9b253266a9da3bd2ebf1e/get-size2/src/lib.rs#L383 it resolves to a call where &**self is a &T, although it conceptually should be an &ArcInner<T>.

hannoman commented 23 hours ago

I tried to write a minimal example crosschecking with a wrapped global allocator, which i would expect to work:

use Ordering::Relaxed;
use std::alloc;
use std::alloc::{GlobalAlloc, Layout};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use get_size2::GetSize;

static USED_MEMORY: AtomicUsize = AtomicUsize::new(0);
struct WrappedRustAllocator;

unsafe impl GlobalAlloc for WrappedRustAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let wrappee = alloc::System;
        let size = layout.size();

        let alloced = wrappee.alloc(layout);
        if !alloced.is_null() {
            USED_MEMORY.fetch_add(size, Relaxed);
        }

        alloced
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        let wrappee = alloc::System;
        let size = layout.size();

        wrappee.dealloc(ptr, layout);
        USED_MEMORY.fetch_sub(size, Relaxed);
    }

    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
        let wrappee = alloc::System;
        let old_size = layout.size();

        let realloced = wrappee.realloc(ptr, layout, new_size);
        if !realloced.is_null() {
            if new_size > old_size {
                USED_MEMORY.fetch_add(new_size - old_size, Relaxed);
            } else {
                USED_MEMORY.fetch_sub(old_size - new_size, Relaxed);
            }
        }

        realloced
    }
}

#[global_allocator]
static RUST_ALLOCATOR: WrappedRustAllocator = WrappedRustAllocator;

fn main() {
    let used_before = USED_MEMORY.load(Relaxed);
    let x = Arc::new(42u64);
    let used_after = USED_MEMORY.load(Relaxed);
    let delta = used_after - used_before;
    assert_eq!(x.get_heap_size(), delta);
}