Skip to content

recycle empty arenas in arena2 and mempool3 allocator#44

Merged
nekevss merged 3 commits intoboa-dev:mainfrom
shruti2522:recycle
Mar 13, 2026
Merged

recycle empty arenas in arena2 and mempool3 allocator#44
nekevss merged 3 commits intoboa-dev:mainfrom
shruti2522:recycle

Conversation

@shruti2522
Copy link
Contributor

  • optimize the memory allocation in both arena2 and mempool3 by holding onto empty memory pages to reuse later, rather than giving them back to the OS immediately
  • also removed the redundant self.allocator.borrow_mut().drop_empty_pools(); call from collect()

@shruti2522 shruti2522 marked this pull request as ready for review March 10, 2026 19:10
Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge conflicts need to be resolved. Here's a couple things I noticed too while looking through the PR.

unsafe { self.inner_ptr.to_typed_arena_pointer::<GcBox<T>>() }
}

#[allow(dead_code)]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: why are these being marked as dead code?

It would be nice to leave a comment, e.g.:

#[allow(dead_code)] // TODO: determine what to do with these or something

heap_threshold: DEFAULT_HEAP_THRESHOLD,
arena_size: DEFAULT_ARENA_SIZE,
arenas: LinkedList::default(),
recycled_arenas: rust_alloc::vec::Vec::new(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: if we are setting a max value. Then we shouldn't use a vec and opt for an array with the upper bound set by a const.

@shruti2522
Copy link
Contributor Author

Merge conflicts need to be resolved. Here's a couple things I noticed too while looking through the PR.

Resolved

Copy link
Member

@nekevss nekevss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a few questions that popped up while looking back through this.

Overall though, this is looking pretty good.


/// Reset arena to its initial empty state, reusing the existing OS buffer.
/// Must only be called when `run_drop_check()` is true (all items dropped).
pub fn reset(&self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: we should probably zero out the memory here to as a proper reset to be "good citizens" so to speak.

I'm not convinced that blocks this PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added write_bytes(0) over the full layout size to zero the buffer. This prevents stale object graphs from being observable through any future partial walk of a recycled arena

for dead_arenas in self.arenas.extract_if(|a| a.run_drop_check()) {
drop(dead_arenas)
let dead_arenas: rust_alloc::vec::Vec<Arena> =
self.arenas.extract_if(|a| a.run_drop_check()).collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we need to collect here?

This returns an iterator correct? So shouldn't we be able to iterate on it directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, collect() was unnecessary overhead here, removed it


// Allocate again, must reuse the recycled arena without growing OS footprint.
// heap_size returns to the same value as when a live arena was present.
for i in 16..32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this show us that the arena is recycled? How do we know it's recycled the same memory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heap_size() equality proves that the OS footprint didn't grow, but you're right it doesnt prove that the recycled slot was actually consumed. Sorry missed that 😅

I have added two recycled_count assertions to the test to verify that,
recycled_count == 1 after drop_dead_arenas() - confirms the arena was saved for reuse instead of being freed.
recycled_count == 0 after the second alloc loop - confirms initialize_new_arena reused that saved arena instead of asking the OS for new memory

Together these two checks confirm that the recycling process worked correctly.

let empties: Vec<SlotPool> = self
.slot_pools
.extract_if(.., |p| p.run_drop_check())
.collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: why can't we use the iterator directly in this instance?

@nekevss nekevss merged commit bb0eb8e into boa-dev:main Mar 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants