-
Notifications
You must be signed in to change notification settings - Fork 54
Make it possible to extend the size of the heap #3
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
Conversation
How is this helpful without some callback asking whether the memory can be extended? I mean the memory can't be used according to the docs, so why not simply add it to the heap from the beginning? |
The intention was that the extend would be called by the user of the heap after allocating additional memory for the new extended heap. I would like to have initial small (bottom, size) area and when the allocation fails I allocate additional virtual address space at the end. Otherwise I would have to provide memory for (bottom, max_size) from the beginning, which might be wasteful if there will not be many allocations. Think of it as a sbrk call on linux |
Ah, yea that makes sense. I got the API backwards in my head. |
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.
Thanks a lot for the PR! Overall, I like the idea of lazily extending the heap size.
It has a large overlap with demand mapping though, especially with the current first fit allocation policy: Instead of creating a small heap and lazily extending its size, one could also create a max_size heap but only map the first few virtual pages to physical memory. When these first few pages are full, a page fault occurs. The OS can then map additional virtual pages to physical memory and restart the operation. This has the big advantage that it is transparent to the application, i.e. the allocation never fails until the complete heap (of size max_size) is full. However, demand mapping also has disadvantages (e.g. doesn't work with best/worst fit policies, requires interrupt handling, makes performance less predictable, …), so I think it's fine to have both possibilities.
I added some comments on the implementation, especially about the increased API surface through the new max_size
field and the HoleList::extend/put_hole
methods.
src/hole.rs
Outdated
@@ -40,6 +40,38 @@ impl HoleList { | |||
} | |||
} | |||
|
|||
/// Places a hole of a given size at a given address after a given hole | |||
unsafe fn put_hole(hole: &mut Hole, addr: usize, size: usize) { | |||
mem::forget(mem::replace(&mut *(addr as *mut Hole), |
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.
Why do we need mem::forget
here?
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.
Not sure if it is really needed here, but isn't it the simillar case as in the new call? It seems to work without the forget call though(because Hole does not have a drop Trait?). Still exploring the mysteries of rust :)
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.
because Hole does not have a drop Trait?
Yes, exactly. I'm not sure why I used it in new
… maybe Hole
had a destructor back then and I forgot to remove it later. In any case, it shouldn't be needed here.
src/hole.rs
Outdated
|
||
/// Extends the size of the list by increasing the size of the last hole | ||
/// or appending new hole at the end of the list | ||
pub fn extend(&mut self, addr: usize, size: usize) { |
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.
Why do we need extend
and put_hole
? Can't we just use the deallocate
function for 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.
Deallocate should work indeed, good catch. Perhaps it's just not a good name for this functionality? How about extend calling deallocate, or should we just get rid of extend call altogether?
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.
How about extend calling deallocate, or should we just get rid of extend call altogether?
I'm fine with both variants.
src/lib.rs
Outdated
@@ -17,6 +17,7 @@ mod test; | |||
pub struct Heap { | |||
bottom: usize, | |||
size: usize, | |||
max_size: usize, |
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.
For what do we need max_size
? The only reason I can see is that this makes it possible to make extend
safe. Maybe it's better to have an unsafe
extend function and let the caller manage max_size
itself.
(Maybe we should even get rid of the bottom
and size
fields… Then we could expand the heap with an arbitrary memory chunk without the need for a single continuous block.)
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.
Thanks for the review! Yes, that makes sense, we could leave it to the user to manage the complexity of the heap size. Also, one can then choose between allocating small heap size or going for on demand mapping. I will post the revised version in the coming days. Cheers
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.
Thanks, looking forward to it! :)
Hello, I updated the pull request. As suggested I removed the max_size field and made the call to the extend unsafe. Also I make use of the deallocate method now (fixed some issue there. If the new hole was added just behind the last hole, the last hole should be merged, instead new hole was being created). I hope changes looks better now :) I have also separate branch where I removed bottom and size fields completely, but feel like it's a big and potentially breaking change for client code so wanted to keep it separate. You can have a look into it here Thanks! |
Thanks a lot for the updates! Looks really good now. I'm a bit short of time at the moment, but I try to take a closer look in the next few days. |
Thanks a lot, no need to hurry :) |
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 left a small improvement suggestion, but otherwise this looks good to me!
src/hole.rs
Outdated
hole.next = Some(unsafe { Unique::new(ptr) }); | ||
if hole_addr + hole.size == addr { | ||
// after: ___XXXFFFF___ where F is the freed block | ||
hole.size += size; |
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.
Good catch! I think we could merge this case with lines 243-249 by replacing the Some(_)
with a None
. Or am I missing something?
Hi Phil, I gave it a second thought and updated the pull request, but instead of None I went with _. I think if we do None, then we will miss the case when the new hole aligns to previous hole, but there are more holes later on the heap? I hope it looks good, please let me know if I can improve it more. Thanks, |
a68d697
to
df307ec
Compare
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.
Thanks for the update!
instead of None I went with _. I think if we do None, then we will miss the case when the new hole aligns to previous hole, but there are more holes later on the heap?
Yeah, I meant to write _
, sorry.
I hope it looks good, please let me know if I can improve it more.
I left a comment about the changed order of match cases. Other than that, I think this is ready to land :).
src/hole.rs
Outdated
@@ -264,6 +257,17 @@ fn deallocate(mut hole: &mut Hole, addr: usize, mut size: usize) { | |||
hole = move_helper(hole).next_unwrap(); // start next iteration at next hole | |||
continue; | |||
} | |||
_ if hole_addr + hole.size == addr => { |
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.
Is there a reason why you moved this match case to the bottom? I think it should work too, but I'm not 100% sure…
(The order matters for some cases, e.g. the XXXFFFFYYYYY
case must come before the XXXFFFF__YYYYY
and XXX__FFFFYYYYY
cases.)
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.
No big reason, just wanted to keep it close to the other _ case, but I am happy to move it back
Thanks!
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.
Ok, then I would move it back
Add extend call that will increase the size of the heap by the given size. This is unsafe call and the caller must ensure that the new area is valid. That allows heap to be more memory efficient, consuming more memory only when necessary, eg. os allocator might map additional virtual memory when the allocation fails.
Hi, I updated the pull request with the requested change to the match statement |
Thanks a lot! |
Published as version 0.2.7 |
Add a max_size parameter to the heap.
Allocations exceeding the size of the heap will fail,
but one can extend the size of the heap with the call to extend.
That allows heap to be more memory efficient, consuming more memory only
when necessary, eg. os allocator might map additional virtual memory when
the allocation fails.