size_of/
lib.rs

1// FIXME: Write better top-level docs
2#![doc = include_str!("../README.md")]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6extern crate alloc;
7
8#[macro_use]
9mod macros;
10mod collections;
11mod core_impls;
12mod human_bytes;
13mod pointers;
14mod std_impls;
15mod support;
16mod tests;
17
18pub use human_bytes::HumanBytes;
19#[cfg(feature = "derive")]
20pub use size_of_derive::SizeOf;
21
22use alloc::{collections::BTreeSet, rc::Rc, sync::Arc};
23use core::{
24    iter::Sum,
25    mem::{replace, size_of_val},
26    ops::{Add, AddAssign, Sub, SubAssign},
27};
28
29// TODO: There's some things we could do with allocator-specific size queries
30// which would allow us to get the "real" size of everything we interact with.
31// Allocators often round up allocation sizes which means that the true size of
32// an allocation can differ from the "declared" size, like an allocator giving
33// back 4096 bytes if a `Box<[u8; 4000]>` was allocated. Ideally we'd report
34// that 4000 bytes were used, but 4096 bytes were allocated in total. This does
35// heavily complicate things since we'd need to add support for as many
36// different allocators as possible such as various malloc implementations,
37// VirtualAlloc(), mimalloc, jemalloc, etc.
38
39/// Get the total size of all given values
40///
41/// ```rust
42/// use core::mem::size_of;
43/// use size_of::SizeOf;
44///
45/// let vector: Vec<u8> = vec![1, 2, 3, 4];
46/// let array: [u8; 10] = [255; 10];
47///
48/// let size = size_of::size_of_values([&vector as &dyn SizeOf, &array as &dyn SizeOf]);
49/// assert_eq!(
50///     size.total_bytes(),
51///     size_of::<Vec<u8>>() + (size_of::<u8>() * 4) + size_of::<[u8; 10]>(),
52/// );
53/// ```
54#[inline]
55pub fn size_of_values<'a, I>(values: I) -> TotalSize
56where
57    I: IntoIterator<Item = &'a dyn SizeOf> + 'a,
58{
59    let mut context = Context::new();
60    values
61        .into_iter()
62        .for_each(|value| value.size_of_with_context(&mut context));
63    context.total_size()
64}
65
66/// Types with a size that can be queried at runtime
67pub trait SizeOf {
68    /// Gets the total size of the current value
69    #[inline]
70    fn size_of(&self) -> TotalSize {
71        let mut context = Context::new();
72        self.size_of_with_context(&mut context);
73        context.total_size()
74    }
75
76    /// Adds the size of the current value to the given [`Context`],
77    /// including both the size of the value itself and all of its children
78    #[inline]
79    fn size_of_with_context(&self, context: &mut Context) {
80        context.add(size_of_val(self));
81        self.size_of_children(context);
82    }
83
84    /// Gets the size of all "children" owned by this value, not including the
85    /// size of the value itself.
86    ///
87    /// This should add all heap allocations owned by the current value to the
88    /// given context
89    fn size_of_children(&self, context: &mut Context);
90}
91
92/// The context of a size query, used to keep track of shared pointers and the
93/// aggregated totals of seen data
94#[derive(Debug, Clone, Default)]
95pub struct Context {
96    /// The total bytes used
97    total_bytes: usize,
98    /// The total excess bytes, e.g. the excess capacity allocated by a `Vec`
99    excess_bytes: usize,
100    /// The total bytes shared between `Rc` and `Arc`-type collections
101    shared_bytes: usize,
102    /// The total number of distinct allocations made
103    distinct_allocations: usize,
104    /// Whether all added bytes should be marked as shared
105    is_shared: bool,
106    /// Keeps track of all pointers (`&T`, `Rc` and `Arc`) we've seen
107    pointers: BTreeSet<usize>,
108}
109
110impl Context {
111    /// Creates a new, empty context
112    #[inline]
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    /// Returns `true` if the current context is shared
118    #[inline]
119    pub const fn is_shared(&self) -> bool {
120        self.is_shared
121    }
122
123    /// Run the given closure and mark all added allocations as shared
124    #[inline]
125    pub fn shared<F>(&mut self, with_shared: F) -> &mut Self
126    where
127        F: FnOnce(&mut Self),
128    {
129        let prev = replace(&mut self.is_shared, true);
130        with_shared(self);
131        self.is_shared = prev;
132        self
133    }
134
135    /// Adds one distinct allocation to the current context
136    #[inline]
137    pub fn add_distinct_allocation(&mut self) -> &mut Self {
138        self.add_distinct_allocations(1)
139    }
140
141    /// Adds `allocations` distinct allocations to the current context
142    #[inline]
143    pub fn add_distinct_allocations(&mut self, allocations: usize) -> &mut Self {
144        self.distinct_allocations += allocations;
145        self
146    }
147
148    /// Adds `size` to the total bytes
149    ///
150    /// - Adds `size` to the shared bytes if the context is currently shared
151    #[inline]
152    pub fn add(&mut self, size: usize) -> &mut Self {
153        self.total_bytes += size;
154        if self.is_shared {
155            self.shared_bytes += size;
156        }
157
158        self
159    }
160
161    /// Adds `size` shared bytes
162    #[inline]
163    pub fn add_shared(&mut self, size: usize) -> &mut Self {
164        self.shared_bytes += size;
165        self
166    }
167
168    /// Adds `size` to the total and excess bytes
169    ///
170    /// - Adds `size` to the shared bytes if the context is currently shared
171    #[inline]
172    pub fn add_excess(&mut self, size: usize) -> &mut Self {
173        self.total_bytes += size;
174        self.excess_bytes += size;
175        if self.is_shared {
176            self.shared_bytes += size;
177        }
178
179        self
180    }
181
182    /// Adds a vector-like object to the current context.
183    ///
184    /// - Adds `len * element_size` to the total bytes
185    /// - Adds `len * element_size` to the shared bytes if the context is
186    ///   currently shared
187    #[inline]
188    pub fn add_arraylike(&mut self, len: usize, element_size: usize) -> &mut Self {
189        let bytes = len * element_size;
190        self.total_bytes += bytes;
191        if self.is_shared {
192            self.shared_bytes += bytes;
193        }
194
195        self
196    }
197
198    /// Adds a vector-like object to the current context.
199    ///
200    /// - Adds `capacity * element_size` to the total bytes
201    /// - Adds `(capacity - len) * element_size` to the excess bytes
202    /// - Adds `capacity * element_size` to the shared bytes if the context is
203    ///   currently shared
204    #[inline]
205    pub fn add_vectorlike(
206        &mut self,
207        len: usize,
208        capacity: usize,
209        element_size: usize,
210    ) -> &mut Self {
211        let used = len * element_size;
212        let allocated = capacity * element_size;
213        self.total_bytes += allocated;
214        self.excess_bytes += allocated - used;
215
216        if self.is_shared {
217            self.shared_bytes += allocated;
218        }
219
220        self
221    }
222
223    /// Returns `true` and adds the given pointer to the current context if it
224    /// hasn't seen it yet. Returns `false` if the current context has seen the
225    /// pointer before
226    #[inline]
227    pub fn insert_ptr<T: ?Sized>(&mut self, ptr: *const T) -> bool {
228        // TODO: Use `pointer::addr()` whenever strict provenance stabilizes
229        self.pointers.insert(ptr as *const T as *const u8 as usize)
230    }
231
232    /// Adds the given pointer to the current context regardless of whether it's
233    /// seen it yet
234    #[inline]
235    pub fn add_ptr<T: ?Sized>(&mut self, ptr: *const T) -> &mut Self {
236        self.insert_ptr(ptr);
237        self
238    }
239
240    /// Returns `true` if the context has seen the given pointer
241    #[inline]
242    pub fn contains_ptr<T: ?Sized>(&self, ptr: *const T) -> bool {
243        // TODO: Use `pointer::addr()` whenever strict provenance stabilizes
244        self.pointers
245            .contains(&(ptr as *const T as *const u8 as usize))
246    }
247
248    // fn insert_ref<T: ?Sized>(&mut self, reference: &T) -> bool {
249    //     self.insert_ptr(reference)
250    // }
251
252    #[inline]
253    fn insert_rc<T: ?Sized>(&mut self, rc: &Rc<T>) -> bool {
254        self.insert_ptr(Rc::as_ptr(rc))
255    }
256
257    #[inline]
258    fn insert_arc<T: ?Sized>(&mut self, arc: &Arc<T>) -> bool {
259        self.insert_ptr(Arc::as_ptr(arc))
260    }
261
262    /// Returns the total size of all objects the current context has seen
263    #[inline]
264    pub const fn total_size(&self) -> TotalSize {
265        TotalSize::new(
266            self.total_bytes,
267            self.excess_bytes,
268            self.shared_bytes,
269            self.distinct_allocations,
270        )
271    }
272}
273
274impl SizeOf for Context {
275    fn size_of_children(&self, context: &mut Context) {
276        self.pointers.size_of_children(context);
277    }
278}
279
280/// Represents the total space taken up by an instance of a variable, including
281/// heap allocations
282#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
283pub struct TotalSize {
284    /// The total bytes used
285    total_bytes: usize,
286    /// The total excess bytes, e.g. the excess capacity allocated by a `Vec`
287    excess_bytes: usize,
288    /// The total bytes shared between `Rc` and `Arc`-type collections
289    shared_bytes: usize,
290    /// The total number of distinct allocations made
291    distinct_allocations: usize,
292}
293
294impl TotalSize {
295    /// Creates a new `TotalSize`
296    #[inline]
297    pub const fn new(
298        total_bytes: usize,
299        excess_bytes: usize,
300        shared_bytes: usize,
301        distinct_allocations: usize,
302    ) -> Self {
303        Self {
304            total_bytes,
305            excess_bytes,
306            shared_bytes,
307            distinct_allocations,
308        }
309    }
310
311    /// Creates a `TotalSize` with a value of zero
312    #[inline]
313    pub const fn zero() -> Self {
314        Self::new(0, 0, 0, 0)
315    }
316
317    /// Sets `total_bytes` to `total` and all others to zero
318    #[inline]
319    pub const fn total(total: usize) -> Self {
320        Self::new(total, 0, 0, 0)
321    }
322
323    /// Returns the total bytes allocated
324    #[inline]
325    pub const fn total_bytes(&self) -> usize {
326        self.total_bytes
327    }
328
329    /// Returns the excess bytes allocated, the unused portions of allocated
330    /// memory
331    #[inline]
332    pub const fn excess_bytes(&self) -> usize {
333        self.excess_bytes
334    }
335
336    /// Returns the number of shared bytes, bytes that are shared behind things
337    /// like `Arc` or `Rc`
338    #[inline]
339    pub const fn shared_bytes(&self) -> usize {
340        self.shared_bytes
341    }
342
343    /// Returns the number of distinct allocations
344    ///
345    /// For example, a `Box<u32>` contains one distinct allocation while a
346    /// `Box<Box<u32>>` contains two
347    #[inline]
348    pub const fn distinct_allocations(&self) -> usize {
349        self.distinct_allocations
350    }
351
352    /// Return the total used bytes, calculated by `total_bytes - excess_bytes`
353    #[inline]
354    pub const fn used_bytes(&self) -> usize {
355        self.total_bytes - self.excess_bytes
356    }
357}
358
359impl Add for TotalSize {
360    type Output = Self;
361
362    #[inline]
363    fn add(self, rhs: Self) -> Self::Output {
364        Self {
365            total_bytes: self.total_bytes + rhs.total_bytes,
366            excess_bytes: self.excess_bytes + rhs.excess_bytes,
367            shared_bytes: self.shared_bytes + rhs.shared_bytes,
368            distinct_allocations: self.distinct_allocations + rhs.distinct_allocations,
369        }
370    }
371}
372
373impl AddAssign for TotalSize {
374    #[inline]
375    fn add_assign(&mut self, rhs: Self) {
376        *self = *self + rhs;
377    }
378}
379
380impl Sub for TotalSize {
381    type Output = Self;
382
383    #[inline]
384    fn sub(self, rhs: Self) -> Self::Output {
385        Self {
386            total_bytes: self.total_bytes - rhs.total_bytes,
387            excess_bytes: self.excess_bytes - rhs.excess_bytes,
388            shared_bytes: self.shared_bytes - rhs.shared_bytes,
389            distinct_allocations: self.distinct_allocations - rhs.distinct_allocations,
390        }
391    }
392}
393
394impl SubAssign for TotalSize {
395    #[inline]
396    fn sub_assign(&mut self, rhs: Self) {
397        *self = *self - rhs;
398    }
399}
400
401impl Sum for TotalSize {
402    #[inline]
403    fn sum<I>(iter: I) -> Self
404    where
405        I: Iterator<Item = Self>,
406    {
407        iter.fold(Self::zero(), |acc, size| acc + size)
408    }
409}
410
411impl SizeOf for TotalSize {
412    fn size_of_children(&self, _context: &mut Context) {}
413}