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:
| Method | Returns |
|---|---|
get_stack_size | The bytes occupied on the stack, by default size_of::<Self>() |
get_heap_size | The bytes this value owns on the heap |
get_size | The 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:
| Tracker | Behavior |
|---|---|
StandardTracker | Remembers every address it has seen. Requires the alloc feature. |
NoTracker | Tracks nothing and always returns the same answer. Used by the untracked methods. |
DefaultTracker | Alias 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:
| Feature | Implemented types |
|---|---|
| (none) | core types, like the primitives, Option, Result, ranges, tuples, arrays, references and raw pointers |
alloc | alloc types, like Vec, String, Box, Rc and Arc |
std | Types 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:
| Feature | Types | Needs std |
|---|---|---|
bytes | Bytes, BytesMut | |
chrono | NaiveDate, NaiveTime, NaiveDateTime, DateTime, TimeDelta, … | |
chrono-tz | TzOffset | |
compact-str | CompactString | |
dashmap | DashMap, DashSet | yes |
half | f16, bf16 | |
hashbrown | HashMap, HashSet, HashTable | |
indexmap | IndexMap, IndexSet | |
ordermap | OrderMap, OrderSet | |
orx-concurrent-vec | ConcurrentVec | |
parking_lot | Mutex, RwLock, also as trackers | yes |
portable-atomic | All portable_atomic atomics, including the 128 bit ones | |
roaring | RoaringBitmap, RoaringTreemap | |
smallvec | SmallVec | |
thin-vec | ThinVec | |
url | Url |
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 borrowedRefCellis reported as0instead of panicking.
Structs§
- NoTracker
- A pseudo tracker which does not track anything.
- Standard
Tracker alloc - 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.
- GetSize
Tracker - A tracker which makes sure that shared ownership objects are only accounted for once.
Functions§
- default_
tracker alloc - Creates the
DefaultTrackerused by the derive macro. - heap_
size - Determines how many bytes the object occupies inside the heap.
Type Aliases§
- Default
Tracker alloc - The tracker used by the
GetSizeimplementations generated by the derive macro.