mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(allocator): Allocator::used_bytes do not use chunk iterator (#21771)
Add a method `Arena::used_bytes` which `Allocator::used_bytes` utilizes. Re-implement it to avoid using `ChunkRawIter` iterator, which is slightly more performant.
This commit is contained in:
1 parent
5199c897a1
commit
7896bd086c
5 files changed
+88
-11
No files matched your search
@@ -569,14 +569,12 @@ impl Allocator {
|
||||
/// [`Vec`]: crate::Vec
|
||||
/// [`StringBuilder`]: crate::StringBuilder
|
||||
/// [`HashMap`]: crate::HashMap
|
||||
//
|
||||
// `#[inline(always)]` because just delegates to `Arena` method
|
||||
#[expect(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
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.arena.iter_allocated_chunks_raw() };
|
||||
for (_, size) in chunks_iter {
|
||||
bytes += size;
|
||||
}
|
||||
bytes
|
||||
self.arena.used_bytes()
|
||||
}
|
||||
|
||||
/// Get inner [`Arena`].
|
||||
|
||||
@@ -163,6 +163,51 @@ impl<const MIN_ALIGN: usize> Arena<MIN_ALIGN> {
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// Calculate the total size of data used in this [`Arena`], in bytes.
|
||||
///
|
||||
/// This is the total amount of memory that has been *used* in the `Arena`, NOT the amount of
|
||||
/// memory the `Arena` owns. If you want the latter, use [`allocated_bytes`] instead.
|
||||
///
|
||||
/// The result includes:
|
||||
/// * Padding bytes within objects allocated in the arena.
|
||||
/// * Padding bytes to preserve alignment of types between objects which have been allocated.
|
||||
///
|
||||
/// [`allocated_bytes`]: Self::allocated_bytes
|
||||
pub fn used_bytes(&self) -> usize {
|
||||
let mut footer_ptr = self.current_chunk_footer_ptr.get();
|
||||
// SAFETY: `self.current_chunk_footer_ptr` always points to a valid `ChunkFooter`
|
||||
let footer = unsafe { footer_ptr.as_ref() };
|
||||
|
||||
// If `Arena` owns no memory, return 0
|
||||
if footer.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get current chunk's used bytes from pointers in `self`, not from the `ChunkFooter`
|
||||
let end_ptr = footer_ptr.cast::<u8>();
|
||||
// SAFETY: `cursor_ptr` is always before or equal to `end_ptr`
|
||||
let mut total = unsafe { end_ptr.offset_from_unsigned(self.cursor_ptr.get()) };
|
||||
|
||||
// Add used bytes from previous chunks, getting pointer from their `ChunkFooter`s
|
||||
footer_ptr = footer.previous_chunk_footer_ptr.get();
|
||||
|
||||
// SAFETY: `footer_ptr` always points to a valid `ChunkFooter`
|
||||
while !unsafe { footer_ptr.as_ref() }.is_empty() {
|
||||
// SAFETY: `footer_ptr` always points to a valid `ChunkFooter`
|
||||
let footer = unsafe { footer_ptr.as_ref() };
|
||||
|
||||
let cursor_ptr = footer.cursor_ptr.get();
|
||||
let end_ptr = footer_ptr.cast::<u8>();
|
||||
debug_assert!(cursor_ptr <= end_ptr);
|
||||
// SAFETY: `cursor_ptr` is always before or equal to `end_ptr`, and both are within the same allocation
|
||||
total += unsafe { end_ptr.offset_from_unsigned(cursor_ptr) };
|
||||
|
||||
footer_ptr = footer.previous_chunk_footer_ptr.get();
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over each chunk of allocated memory that an arena has allocated into.
|
||||
|
||||
@@ -270,7 +270,7 @@ struct ChunkFooter {
|
||||
/// Bump allocation cursor, valid only for retired (non-current) chunks.
|
||||
/// This field is written when a chunk is retired (when a new chunk is created).
|
||||
/// Allocation methods use `Arena::cursor_ptr` instead, which is the authoritative pointer for current chunk.
|
||||
/// This field is only used in `ChunkIter` and `ChunkRawIter` iterators.
|
||||
/// This field is only used in `ChunkIter` and `ChunkRawIter` iterators, and `used_bytes` method.
|
||||
cursor_ptr: Cell<NonNull<u8>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ fn arena_not_sync() {}
|
||||
|
||||
// Uses private `DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER`, `OVERHEAD`, and `TYPICAL_PAGE_SIZE`
|
||||
#[test]
|
||||
fn allocated_bytes() {
|
||||
fn allocated_and_used_bytes() {
|
||||
fn chunk_count<const MIN_ALIGN: usize>(b: &mut Arena<MIN_ALIGN>) -> usize {
|
||||
b.iter_allocated_chunks().count()
|
||||
}
|
||||
@@ -44,30 +44,36 @@ fn allocated_bytes() {
|
||||
|
||||
// Empty arena owns no chunks
|
||||
assert_eq!(b.allocated_bytes(), 0);
|
||||
assert_eq!(b.used_bytes(), 0);
|
||||
assert_eq!(chunk_count(&mut b), 0);
|
||||
|
||||
// First allocation creates the first chunk
|
||||
b.alloc(0u8);
|
||||
assert_eq!(b.allocated_bytes(), DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
|
||||
assert_eq!(b.used_bytes(), 1);
|
||||
assert_eq!(chunk_count(&mut b), 1);
|
||||
|
||||
// Reset with a single chunk: The chunk is retained (capacity unchanged), but the cursor is reset
|
||||
b.reset();
|
||||
assert_eq!(b.allocated_bytes(), DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
|
||||
assert_eq!(b.used_bytes(), 0);
|
||||
assert_eq!(chunk_count(&mut b), 1);
|
||||
|
||||
// Allocations that fit in the current chunk don't grow the total
|
||||
// Allocations that fit in the current chunk don't grow the allocated total, but do grow `used_bytes`
|
||||
let small = Layout::from_size_align(64, 1).unwrap();
|
||||
b.alloc_layout(small);
|
||||
b.alloc_layout(small);
|
||||
assert_eq!(b.allocated_bytes(), DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER);
|
||||
assert_eq!(b.used_bytes(), small.size() * 2);
|
||||
assert_eq!(chunk_count(&mut b), 1);
|
||||
|
||||
// A request which cannot be served in first chunk forces creation of a second chunk
|
||||
// A request which cannot be served in first chunk forces creation of a second chunk.
|
||||
// Chunk 1 is retired with its current `used_bytes` (`2 * 64`); chunk 2 holds the `big` allocation.
|
||||
let big = Layout::from_size_align(DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER, 1).unwrap();
|
||||
b.alloc_layout(big);
|
||||
let two_chunks = b.allocated_bytes();
|
||||
assert_eq!(two_chunks, DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER + SECOND_CHUNK);
|
||||
assert_eq!(b.used_bytes(), small.size() * 2 + big.size());
|
||||
assert_eq!(chunk_count(&mut b), 2);
|
||||
|
||||
// A request larger than every chunk seen so far forces a third chunk.
|
||||
@@ -77,6 +83,7 @@ fn allocated_bytes() {
|
||||
b.alloc_layout(huge);
|
||||
let three_chunks = b.allocated_bytes();
|
||||
assert_eq!(three_chunks, DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER + SECOND_CHUNK + THIRD_CHUNK);
|
||||
assert_eq!(b.used_bytes(), small.size() * 2 + big.size() + huge.size());
|
||||
assert_eq!(chunk_count(&mut b), 3);
|
||||
|
||||
// `reset` with multiple chunks deallocates every chunk except the most recent (which is also the largest),
|
||||
@@ -84,6 +91,7 @@ fn allocated_bytes() {
|
||||
b.reset();
|
||||
let after_reset = b.allocated_bytes();
|
||||
assert_eq!(after_reset, THIRD_CHUNK);
|
||||
assert_eq!(b.used_bytes(), 0);
|
||||
assert_eq!(chunk_count(&mut b), 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -273,4 +273,30 @@ quickcheck! {
|
||||
}
|
||||
}
|
||||
|
||||
fn used_bytes_tracking(allocs: Vec<usize>) -> () {
|
||||
let mut b = Arena::new();
|
||||
let mut slice_bytes = 0;
|
||||
for len in allocs {
|
||||
const MAX_LEN: usize = 512;
|
||||
let len = len % MAX_LEN;
|
||||
b.alloc_slice_fill_copy(len, 0u8);
|
||||
slice_bytes += len;
|
||||
|
||||
// `Arena<1>` with `align == 1` allocations: cursor advances by exactly `len` per call,
|
||||
// so total used bytes equal the sum of requested sizes
|
||||
let used_bytes = b.used_bytes();
|
||||
assert_eq!(used_bytes, slice_bytes);
|
||||
|
||||
// `used_bytes <= allocated_bytes` (capacity >= used)
|
||||
assert!(used_bytes <= b.allocated_bytes());
|
||||
|
||||
// `used_bytes` agrees with summing each chunk's used portion via `iter_allocated_chunks`
|
||||
let from_iter = b.iter_allocated_chunks().map(<[_]>::len).sum();
|
||||
assert_eq!(used_bytes, from_iter);
|
||||
}
|
||||
|
||||
// After `reset`, the surviving chunk's cursor is back at its end - 0 bytes used
|
||||
b.reset();
|
||||
assert_eq!(b.used_bytes(), 0);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user