Skip to content

Commit

Permalink
feat(allocator): add Allocator::capacity and used_bytes methods (#…
Browse files Browse the repository at this point in the history
…8621)

Add 2 methods for determining the size of `Allocator`:

* `capacity` returns total size of memory owned by the `Allocator` (including space not yet used).
* `used_bytes` returns total size of data so far allocated in the arena.
  • Loading branch information
overlookmotel committed Jan 20, 2025
1 parent c9f3c5f commit 2a2ad53
Showing 1 changed file with 94 additions and 0 deletions.
94 changes: 94 additions & 0 deletions crates/oxc_allocator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,100 @@ impl Allocator {
self.bump.reset();
}

/// Calculate the total capacity of this [`Allocator`] including all chunks, in bytes.
///
/// Note: This is the total amount of memory the [`Allocator`] owns NOT the total size of data
/// that's been allocated in it. If you want the latter, use [`used_bytes`] instead.
///
/// # Examples
/// ```
/// use oxc_allocator::Allocator;
///
/// let capacity = 64 * 1024; // 64 KiB
/// let mut allocator = Allocator::with_capacity(capacity);
/// allocator.alloc(123u64); // 8 bytes
///
/// // Result is the capacity (64 KiB), not the size of allocated data (8 bytes).
/// // `Allocator::with_capacity` may allocate a bit more than requested.
/// assert!(allocator.capacity() >= capacity);
/// ```
///
/// [`used_bytes`]: Allocator::used_bytes
//
// `#[inline(always)]` because it just delegates to `bumpalo`
#[expect(clippy::inline_always)]
#[inline(always)]
pub fn capacity(&self) -> usize {
self.bump.allocated_bytes()
}

/// Calculate the total size of data used in this [`Allocator`], in bytes.
///
/// This is the total amount of memory that has been *used* in the [`Allocator`], NOT the amount of
/// memory the [`Allocator`] owns. If you want the latter, use [`capacity`] instead.
///
/// The result includes:
///
/// 1. Padding bytes between objects which have been allocated to preserve alignment of types
/// where they have different alignments or have larger-than-typical alignment.
/// 2. Excess capacity in [`Vec`]s, [`String`]s and [`HashMap`]s.
/// 3. Objects which were allocated but later dropped. [`Allocator`] does not re-use allocations,
/// so anything which is allocated into arena continues to take up "dead space", even after it's
/// no longer referenced anywhere.
/// 4. "Dead space" left over where a [`Vec`], [`String`] or [`HashMap`] has grown and had to make
/// a new allocation to accommodate its new larger size. Its old allocation continues to take up
/// "dead" space in the allocator, unless it was the most recent allocation.
///
/// In practice, this almost always means that the result returned from this function will be an
/// over-estimate vs the amount of "live" data in the arena.
///
/// However, if you are using the result of this method to create a new `Allocator` to clone
/// an AST into, it is theoretically possible (though very unlikely) that it may be a slight
/// under-estimate of the capacity required in new allocator to clone the AST into, depending
/// on the order that `&str`s were allocated into arena in parser vs the order they get allocated
/// during cloning. The order allocations are made in affects the amount of padding bytes required.
///
/// # Examples
/// ```
/// use oxc_allocator::{Allocator, Vec};
///
/// let capacity = 64 * 1024; // 64 KiB
/// let mut allocator = Allocator::with_capacity(capacity);
///
/// allocator.alloc(1u8); // 1 byte with alignment 1
/// allocator.alloc(2u8); // 1 byte with alignment 1
/// allocator.alloc(3u64); // 8 bytes with alignment 8
///
/// // Only 10 bytes were allocated, but 16 bytes were used, in order to align `3u64` on 8
/// assert_eq!(allocator.used_bytes(), 16);
///
/// allocator.reset();
///
/// let mut vec = Vec::<u64>::with_capacity_in(2, &allocator);
///
/// // Allocate something else, so `vec`'s allocation is not the most recent
/// allocator.alloc(123u64);
///
/// // `vec` has to grow beyond it's initial capacity
/// vec.extend([1, 2, 3, 4]);
///
/// // `vec` takes up 32 bytes, and `123u64` takes up 8 bytes = 40 total.
/// // But there's an additional 16 bytes consumed for `vec`'s original capacity of 2,
/// // which is still using up space
/// assert_eq!(allocator.used_bytes(), 56);
/// ```
///
/// [`capacity`]: Allocator::capacity
pub fn used_bytes(&self) -> usize {
let mut bytes = 0;
// SAFETY: No allocations are made while `chunks_iter` is alive. No data is read from the chunks.
let chunks_iter = unsafe { self.bump.iter_allocated_chunks_raw() };
for (_, size) in chunks_iter {
bytes += size;
}
bytes
}

/// Get inner [`bumpalo::Bump`].
///
/// This method is not public. We don't want to expose `bumpalo::Allocator` to user.
Expand Down

0 comments on commit 2a2ad53

Please sign in to comment.