Skip to main content

Crate get_size2

Crate get_size2 

Source
Expand description

Determine the size in bytes an object occupies inside RAM.

size_of already reports how many bytes a value occupies on the stack. Applications like caches or memory budgets also need to know how many bytes a value owns on the heap, which is what the GetSize trait adds.

get-size2 = { version = "^0.10", features = ["derive"] }

§Quick start

use get_size2::GetSize;

let value = String::from("Hello World!");

// The 12 bytes of the string data live on the heap.
assert_eq!(value.get_heap_size(), 12);
assert_eq!(value.get_size(), String::get_stack_size() + 12);

// Only the allocated capacity is accounted for, not the length.
let mut buffer = Vec::with_capacity(1024);
buffer.push(1u8);
assert_eq!(buffer.get_heap_size(), 1024);

The trait provides three methods:

MethodReturns
get_stack_sizeThe bytes occupied on the stack, by default size_of::<Self>()
get_heap_sizeThe bytes this value owns on the heap
get_sizeThe sum of both

It is implemented for the primitives and for most types of core, alloc and std, for your own types via the derive macro, and for a number of popular third party types behind feature flags.

§Ownership based accounting

Only bytes owned by a value are accounted for. Borrowed bytes belong to their owner, so anything reached through a reference or a raw pointer counts as zero:

use get_size2::GetSize;

#[derive(GetSize)]
struct Borrowing<'a> {
    value: &'a String,
}

let value = String::from("hello");
assert_eq!(value.get_heap_size(), 5);

// The struct only borrows the string, so its heap bytes are not counted.
let borrowing = Borrowing { value: &value };
assert_eq!(borrowing.get_heap_size(), 0);

Beware of auto-dereferencing when calling the method on a reference yourself, since the compiler will resolve it to the referenced value:

use get_size2::GetSize;

let value = String::from("hello");

assert_eq!((&value).get_heap_size(), 5); // Rewritten to `value.get_heap_size()`!
assert_eq!(GetSize::get_heap_size(&&value), 0); // Fully qualified syntax, no deref.
assert_eq!(get_size2::heap_size(&&value), 0); // Or use the free function.

The derive macro always uses fully qualified syntax, so derived implementations are not affected.

Shared ownership is the one exception to the borrowing rule: an Rc or Arc genuinely owns its allocation, so its contents are counted, including the stack bytes of the value it points to.

use get_size2::GetSize;
use std::sync::Arc;

let value = Arc::new(String::from("hello"));
assert_eq!(value.get_heap_size(), String::get_stack_size() + 5);

§Tracking shared ownership

Because shared ownership is counted, a structure holding several handles to the same allocation would account for it more than once. A GetSizeTracker prevents that by remembering which addresses it has already seen:

use get_size2::{GetSize, StandardTracker};
use std::sync::Arc;

let shared = Arc::new(String::from("hello"));
let pair = (Arc::clone(&shared), Arc::clone(&shared));

let single = String::get_stack_size() + 5;

// Without a tracker, both handles are accounted for.
assert_eq!(pair.get_heap_size(), 2 * single);

// With a tracker, the shared allocation is only counted once.
let (size, _tracker) = pair.get_heap_size_with_tracker(StandardTracker::new());
assert_eq!(size, single);

Derived implementations track by default: the get_heap_size generated by the derive macro starts a fresh default_tracker and passes it through all fields.

use get_size2::GetSize;
use std::sync::Arc;

#[derive(GetSize)]
struct Shared {
    first: Arc<String>,
    second: Arc<String>,
}

let shared = Arc::new(String::from("hello"));
let value = Shared {
    first: Arc::clone(&shared),
    second: Arc::clone(&shared),
};

assert_eq!(value.get_heap_size(), String::get_stack_size() + 5);

Three trackers ship with this crate:

TrackerBehavior
StandardTrackerRemembers every address it has seen. Requires the alloc feature.
NoTrackerTracks nothing and always returns the same answer. Used by the untracked methods.
DefaultTrackerAlias for whichever of the two the derive macro can use, see default_tracker.

GetSizeTracker is also implemented for &mut T, Box<T>, Mutex<T>, RwLock<T>, Arc<Mutex<T>> and Arc<RwLock<T>> of any tracker, so a single tracker can be reused across calls or shared between threads. Implement the trait yourself if you need different bookkeeping.

§Deriving GetSize

Enable the derive feature and add #[derive(GetSize)] to your struct or enum. The generated implementation sums up the heap size of every field:

use get_size2::GetSize;

#[derive(GetSize)]
struct Data {
    name: String,
    id: u64,
}

let data = Data { name: "Adam".into(), id: 1 };
assert_eq!(data.get_heap_size(), 4);

Fields whose type does not implement GetSize can be ignored, given a fixed size, or measured by a helper function using the #[get_size(...)] attribute. See the get-size-derive2 documentation for the full attribute reference. Unions are not supported.

§Implementing GetSize manually

Only get_heap_size_with_tracker needs to be implemented; the other methods have suitable default implementations. The default returns 0, which is already correct for any type that does not own heap memory.

use get_size2::{GetSize, GetSizeTracker};

struct Buffer {
    data: Vec<u8>,
    len: usize,
}

impl GetSize for Buffer {
    fn get_heap_size_with_tracker<T: GetSizeTracker>(&self, tracker: T) -> (usize, T) {
        // Pass the tracker on so shared ownership deeper in the structure stays deduplicated.
        self.data.get_heap_size_with_tracker(tracker)
    }
}

let buffer = Buffer { data: Vec::with_capacity(64), len: 0 };
assert_eq!(buffer.get_heap_size(), 64);

§Feature flags

§no_std support

This crate is #![no_std]. Which standard library implementations are available is controlled by two features, both enabled by the std default feature:

FeatureImplemented types
(none)core types, like the primitives, Option, Result, ranges, tuples, arrays, references and raw pointers
allocalloc types, like Vec, String, Box, Rc and Arc
stdTypes only provided by std, like HashMap, PathBuf or Mutex

To build for a no_std target, disable the default features:

get-size2 = { version = "^0.10", default-features = false, features = ["alloc", "derive"] }

Without alloc there is nothing to allocate the set of seen addresses with, so StandardTracker is unavailable and default_tracker falls back to NoTracker. That is sufficient, because there is no shared ownership type to track in the first place.

§Integrations

Each of these features implements GetSize for the types of the respective crate:

FeatureTypesNeeds std
bytesBytes, BytesMut
chronoNaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeDelta, …
chrono-tzTzOffset
compact-strCompactString
dashmapDashMap, DashSetyes
halff16, bf16
hashbrownHashMap, HashSet, HashTable
indexmapIndexMap, IndexSet
ordermapOrderMap, OrderSet
orx-concurrent-vecConcurrentVec
parking_lotMutex, RwLock, also as trackersyes
portable-atomicAll portable_atomic atomics, including the 128 bit ones
roaringRoaringBitmap, RoaringTreemap
smallvecSmallVec
thin-vecThinVec
urlUrl

Features marked as needing std enable it implicitly. all-features-no-std is a convenience pack activating everything which works without std, that is every feature except dashmap and parking_lot.

§Accuracy

The reported sizes are a best effort based on the public API of the measured types:

  • Unused capacity is counted, since it is allocated. Small string and small vector optimizations are respected, so inline data is not double counted.
  • Allocator bookkeeping, padding between allocations and the internal node overhead of tree and hash based collections are not counted, so results are a lower bound on what the allocator actually reserved.
  • Values behind a raw pointer are not counted, since ownership cannot be determined.
  • A locking type (Mutex, RwLock, RefCell) is measured by reading its content. A poisoned lock is measured anyway, while an already mutably borrowed RefCell is reported as 0 instead of panicking.

Structs§

NoTracker
A pseudo tracker which does not track anything.
StandardTrackeralloc
A simple standard tracker which can be used to track shared ownership references.

Traits§

GetSize
Determine the size in bytes an object occupies inside RAM.
GetSizeTracker
A tracker which makes sure that shared ownership objects are only accounted for once.

Functions§

default_trackeralloc
Creates the DefaultTracker used by the derive macro.
heap_size
Determines how many bytes the object occupies inside the heap.

Type Aliases§

DefaultTrackeralloc
The tracker used by the GetSize implementations generated by the derive macro.

Derive Macros§

GetSizederive
Derives the GetSize trait for structs and enums.