From 50face81c7d9c518289fe3ad033f26273a6f8dc2 Mon Sep 17 00:00:00 2001 From: Simon Sapin <simon.sapin@exyr.org> Date: Sun, 6 Mar 2016 13:49:07 +0100 Subject: [PATCH] Implement a lower bound for Arc<T> and Rc<T> This crate has always given a lower bound of the actual heap memory usage: we use `#[ignore_heap_size_of]` when not doing it is not practical. This fix #37 with one of the ideas given in the discussion there: return an accurate result when the reference count is 1 (and ownership is in fact not shared), or zero otherwise. However, since APIs to access reference counts are not `#[stable]` yet (see https://github.com/rust-lang/rust/issues/28356), so always return zero when unstable APIs are not enabled. --- src/lib.rs | 50 +++++++++++++++++++++++++++++++++++++------------- tests/tests.rs | 10 ++++++---- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 43551a9..59a8f08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ //! Data structure measurement. +#![cfg_attr(feature = "unstable", feature(rc_counts, arc_counts))] + #[cfg(target_os = "windows")] extern crate kernel32; @@ -16,7 +18,7 @@ use std::collections::{BTreeMap, HashMap, LinkedList, VecDeque}; use std::hash::BuildHasher; use std::hash::Hash; use std::marker::PhantomData; -use std::mem::size_of; +use std::mem::{size_of, size_of_val}; use std::net::{Ipv4Addr, Ipv6Addr}; use std::os::raw::c_void; use std::sync::Arc; @@ -45,7 +47,7 @@ unsafe fn heap_size_of_impl(ptr: *const c_void) -> usize { // this function doesn't modify the contents of the block that `ptr` points to, so we use // `*const c_void` here. extern "C" { - #[cfg_attr(any(prefixed_jemalloc, target_os = "macos", target_os = "android"), link_name = "je_malloc_usable_size")] + #[cfg_attr(any(prefixed_jemalloc, target_os = "macos", target_os = "android"), link_name = "je_malloc_usable_size")] fn malloc_usable_size(ptr: *const c_void) -> usize; } malloc_usable_size(ptr) @@ -150,12 +152,6 @@ impl<T: HeapSizeOf, U: HeapSizeOf> HeapSizeOf for (T, U) { } } -impl<T: HeapSizeOf> HeapSizeOf for Arc<T> { - fn heap_size_of_children(&self) -> usize { - (**self).heap_size_of_children() - } -} - impl<T: HeapSizeOf> HeapSizeOf for RefCell<T> { fn heap_size_of_children(&self) -> usize { self.borrow().heap_size_of_children() @@ -185,16 +181,44 @@ impl<T: HeapSizeOf> HeapSizeOf for VecDeque<T> { } } -impl<T> HeapSizeOf for Vec<Rc<T>> { +impl<T: HeapSizeOf> HeapSizeOf for Rc<T> { + #[cfg(not(feature = "unstable"))] fn heap_size_of_children(&self) -> usize { - // The fate of measuring Rc<T> is still undecided, but we still want to measure - // the space used for storing them. - unsafe { - heap_size_of(self.as_ptr() as *const c_void) + 0 + } + + #[cfg(feature = "unstable")] + fn heap_size_of_children(&self) -> usize { + if Rc::strong_count(self) == 1 { + heap_size_of_rcbox::<T>(self) + } else { + 0 } } } +impl<T: HeapSizeOf> HeapSizeOf for Arc<T> { + #[cfg(not(feature = "unstable"))] + fn heap_size_of_children(&self) -> usize { + 0 + } + + #[cfg(feature = "unstable")] + fn heap_size_of_children(&self) -> usize { + if Arc::strong_count(self) == 1 { + heap_size_of_rcbox::<T>(self) + } else { + 0 + } + } +} + +/// Arc<T> and Rc<T> heap-allocate `(usize, usize, T)` with strong and weak refcounts. +#[cfg(feature = "unstable")] +fn heap_size_of_rcbox<T: ?Sized + HeapSizeOf>(x: &T) -> usize { + size_of::<usize>() * 2 + size_of_val(x) + x.heap_size_of_children() +} + #[cfg(feature = "unstable")] impl<K: HeapSizeOf, V: HeapSizeOf, S> HeapSizeOf for HashMap<K, V, S> where K: Eq + Hash, S: BuildHasher { diff --git a/tests/tests.rs b/tests/tests.rs index 67ca98d..60b603f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -7,6 +7,7 @@ extern crate heapsize; use heapsize::{HeapSizeOf, heap_size_of}; +use std::mem::size_of; use std::os::raw::c_void; pub const EMPTY: *mut () = 0x1 as *mut (); @@ -141,13 +142,14 @@ fn test_heap_size() { let x = Some(Box::new(0i64)); assert_eq!(x.heap_size_of_children(), 8); - // Not on the heap. let x = ::std::sync::Arc::new(0i64); - assert_eq!(x.heap_size_of_children(), 0); + assert_eq!(x.heap_size_of_children(), + if cfg!(feature = "unstable") { size_of::<usize>() * 2 + 8 } else { 0 }); - // The `Arc` is not on the heap, but the Box is. + // `ArcInner<Box<_>>` has 2 refcounts + a pointer on the heap. let x = ::std::sync::Arc::new(Box::new(0i64)); - assert_eq!(x.heap_size_of_children(), 8); + assert_eq!(x.heap_size_of_children(), + if cfg!(feature = "unstable") { size_of::<usize>() * 3 + 8 } else { 0 }); // Zero elements, no heap storage. let x: Vec<i64> = vec![];