-
Notifications
You must be signed in to change notification settings - Fork 221
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
base: master
Are you sure you want to change the base?
Changes from all commits
9eb9cd4
c4b96cc
a637bbe
b6f6931
83a1047
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -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>, | ||
|
@@ -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 { | ||
|
@@ -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 { | ||
let this = &*ManuallyDrop::new(inner); | ||
(read(&this.allocator), read(&this.segments)) | ||
}; | ||
allocator | ||
} | ||
} | ||
|
||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks wrong to me. I pushed some new doc comments for I suppose one things that's potentially confusing about all this is that the word "allocate" is being used for different things:
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dwrensha would |
||
}); | ||
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); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
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 thatRefCell
to me is the big win of all this.There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#365