Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor builder arena #362

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions capnp/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,7 @@ where

fn get_root_internal(&mut self) -> any_pointer::Builder<'_> {
if self.arena.is_empty() {
self.arena
.allocate_segment(1)
.expect("allocate root pointer");
self.arena.allocate(0, 1).expect("allocate root pointer");
self.arena.allocate_segment(1);
}
let (seg_start, _seg_len) = self.arena.get_segment_mut(0);
let location: *mut u8 = seg_start;
Expand Down Expand Up @@ -463,10 +460,7 @@ where
/// a single segment, containing the full canonicalized message.
pub fn set_root_canonical<From: SetPointerBuilder>(&mut self, value: From) -> Result<()> {
if self.arena.is_empty() {
self.arena
.allocate_segment(1)
.expect("allocate root pointer");
self.arena.allocate(0, 1).expect("allocate root pointer");
self.arena.allocate_segment(1);
}
let (seg_start, _seg_len) = self.arena.get_segment_mut(0);
let pointer = layout::PointerBuilder::get_root(&self.arena, 0, seg_start);
Expand Down
72 changes: 37 additions & 35 deletions capnp/src/private/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::mem::ManuallyDrop;
use core::ptr::read;
use core::slice;
use core::u64;

Expand Down Expand Up @@ -173,11 +175,23 @@ struct BuilderSegment {
allocated: u32, // in words
}

impl BuilderSegment {
fn allocate(&mut self, amount: WordCount32) -> Option<u32> {
if amount > self.capacity - self.allocated {
None
} else {
let result = self.allocated;
self.allocated += amount;
Some(result)
}
}
}

pub struct BuilderArenaImplInner<A>
where
A: Allocator,
{
allocator: Option<A>, // None if has already be deallocated.
allocator: A,

// TODO(perf): Try using smallvec to avoid heap allocations in the single-segment case?
segments: Vec<BuilderSegment>,
Expand All @@ -197,15 +211,15 @@ where
pub fn new(allocator: A) -> Self {
Self {
inner: RefCell::new(BuilderArenaImplInner {
allocator: Some(allocator),
allocator: allocator,
segments: Vec::new(),
}),
}
}

/// Allocates a new segment with capacity for at least `minimum_size` words.
pub fn allocate_segment(&self, minimum_size: u32) -> Result<()> {
self.inner.borrow_mut().allocate_segment(minimum_size)
pub fn allocate_segment(&self, minimum_size: u32) {
self.inner.borrow_mut().allocate_segment(minimum_size);
}

pub fn get_segments_for_output(&self) -> OutputSegments {
Expand Down Expand Up @@ -247,7 +261,12 @@ where
pub fn into_allocator(self) -> A {
let mut inner = self.inner.into_inner();
inner.deallocate_all();
inner.allocator.take().unwrap()
// See https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/.E2.9C.94.20Extracting.20field.20from.20struct.20that.20implement.20.60Drop.60
let (allocator, _segments) = unsafe {
Copy link
Member

@dwrensha dwrensha Jan 16, 2023

Choose a reason for hiding this comment

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

I'm hesitant to add more unsafe like this. I'm tempted to pull in the changes from #363 without the changes from this PR, to see if that still works well. Getting rid of that RefCell to me is the big win of all this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As you wish :-) I will likely be testing all those changes in production in the next couple of weeks.

I do think simplifying the codebase, is worth one unsafe block though.

Copy link
Member

Choose a reason for hiding this comment

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

let this = &*ManuallyDrop::new(inner);
(read(&this.allocator), read(&this.segments))
};
allocator
}
}

Expand Down Expand Up @@ -288,54 +307,37 @@ where
A: Allocator,
{
/// Allocates a new segment with capacity for at least `minimum_size` words.
fn allocate_segment(&mut self, minimum_size: WordCount32) -> Result<()> {
let seg = match &mut self.allocator {
Some(a) => a.allocate_segment(minimum_size),
None => unreachable!(),
};
fn allocate_segment(&mut self, minimum_size: WordCount32) -> SegmentId {
let seg = self.allocator.allocate_segment(minimum_size);
let segment_id = self.segments.len() as u32;
self.segments.push(BuilderSegment {
ptr: seg.0,
capacity: seg.1,
allocated: 0,
allocated: minimum_size,
Copy link
Member

Choose a reason for hiding this comment

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

This looks wrong to me. allocated is supposed to indicate how many words of the segment are already in use, which should be zero when the segment is first allocated. It seems to me that what you have here would result in a lot of unused memory.

I pushed some new doc comments for BuilderSegment that might make things clearer: 8888443

I suppose one things that's potentially confusing about all this is that the word "allocate" is being used for different things:

  1. the message::Allocator, whose job is to provide memory from the system
  2. BuilderArena::allocate(), which takes takes memory already-"allocated" memory from (1) and reserves it for usage by private::layout.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see. This has to do with commit b6f6931. It happens to work because there are only a two usages of the allocate_segment() method, and they follow a particular pattern.

I'd prefer not to make this change. If we do make it, we should at least add some comments about the new behavior, and maybe also change some naming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we can introduce a dedicated method for this then? I think having a non fallible method helps with readability and optimizations

Copy link
Contributor Author

@marmeladema marmeladema Jan 16, 2023

Choose a reason for hiding this comment

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

@dwrensha would allocate_full_segment be a suitable name for this method? I am not very good with names 😅 Given that this is a private method, maybe it doesn't matter too much? Once decided I will also update the doc comment

});
Ok(())
segment_id
}

fn allocate(&mut self, segment_id: u32, amount: WordCount32) -> Option<u32> {
let seg = &mut self.segments[segment_id as usize];
if amount > seg.capacity - seg.allocated {
None
} else {
let result = seg.allocated;
seg.allocated += amount;
Some(result)
}
self.segments[segment_id as usize].allocate(amount)
}

fn allocate_anywhere(&mut self, amount: u32) -> (SegmentId, u32) {
// first try the existing segments, then try allocating a new segment.
let allocated_len = self.segments.len() as u32;
for segment_id in 0..allocated_len {
if let Some(idx) = self.allocate(segment_id, amount) {
return (segment_id, idx);
for (segment_id, segment) in self.segments.iter_mut().enumerate() {
if let Some(idx) = segment.allocate(amount) {
return (segment_id as u32, idx);
}
}

// Need to allocate a new segment.

self.allocate_segment(amount).expect("allocate new segment");
(
allocated_len,
self.allocate(allocated_len, amount)
.expect("use freshly-allocated segment"),
)
(self.allocate_segment(amount), 0)
}

fn deallocate_all(&mut self) {
if let Some(a) = &mut self.allocator {
for seg in &self.segments {
a.deallocate_segment(seg.ptr, seg.capacity, seg.allocated);
}
for seg in &self.segments {
self.allocator
.deallocate_segment(seg.ptr, seg.capacity, seg.allocated);
}
}

Expand Down