-
Notifications
You must be signed in to change notification settings - Fork 9
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
Implement AllocRef for references to A: AllocRef #53
Comments
I think this is fine to add. |
@lachlansneff Do you want to push this upstream should I take care if it? |
However, I don't see, why composable allocators can't be implemented with the above definition? Your linked gist has a bug in // reused in multiple allocators
unsafe fn grow<A1: AllocRef, A2: AllocRef>(
a1: &mut A1,
a2: &mut A2,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<MemoryBlock, AllocErr> {
if placement == ReallocPlacement::MayMove {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_memory = a2.alloc(new_layout, init)?;
ptr::copy_nonoverlapping(ptr.as_ptr(), new_memory.ptr.as_ptr(), layout.size());
a1.dealloc(ptr, layout);
Ok(new_memory)
} else {
Err(AllocErr)
}
}
// reused in multiple allocators
unsafe fn shrink<A1: AllocRef, A2: AllocRef>(
a1: &mut A1,
a2: &mut A2,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
) -> Result<MemoryBlock, AllocErr> {
if placement == ReallocPlacement::MayMove {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
let new_memory = a2.alloc(new_layout, AllocInit::Uninitialized)?;
ptr::copy_nonoverlapping(ptr.as_ptr(), new_memory.ptr.as_ptr(), new_memory.size);
a1.dealloc(ptr, layout);
Ok(new_memory)
} else {
Err(AllocErr)
}
}
#[derive(Debug, Copy, Clone)]
pub struct SegregateAlloc<Small, Large> {
threshold: usize,
pub small: Small,
pub large: Large,
}
impl<Small: AllocRef, Large: AllocRef> SegregateAlloc<Small, Large> {
fn clamp_memory(&self, memory: MemoryBlock) -> MemoryBlock {
MemoryBlock {
ptr: memory.ptr,
size: cmp::max(memory.size, self.threshold),
}
}
}
unsafe impl<Small, Large> AllocRef for SegregateAlloc<Small, Large>
where
Small: AllocRef,
Large: AllocRef,
{
fn alloc(&mut self, layout: Layout, init: AllocInit) -> Result<MemoryBlock, AllocErr> {
if layout.size() <= self.threshold {
let memory = self.small.alloc(layout, init)?;
// memory block must be smaller than threshold
Ok(self.clamp_memory(memory))
} else {
self.large.alloc(layout, init)
}
}
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
if layout.size() <= self.threshold {
self.small.dealloc(ptr, layout)
} else {
self.large.dealloc(ptr, layout)
}
}
unsafe fn grow(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
init: AllocInit,
) -> Result<MemoryBlock, AllocErr> {
if layout.size() <= self.threshold {
let memory = if new_size > self.threshold {
// Move ownership to `self.large`
grow(
&mut self.small,
&mut self.large,
ptr,
layout,
new_size,
placement,
init,
)?
} else {
self.small.grow(ptr, layout, new_size, placement, init)?
};
Ok(self.clamp_memory(memory))
} else {
self.large.grow(ptr, layout, new_size, placement, init)
}
}
unsafe fn shrink(
&mut self,
ptr: NonNull<u8>,
layout: Layout,
new_size: usize,
placement: ReallocPlacement,
) -> Result<MemoryBlock, AllocErr> {
let memory = if layout.size() <= self.threshold {
let memory = self.small.shrink(ptr, layout, new_size, placement)?;
Ok(self.clamp_memory(memory))
} else if new_size <= self.threshold {
// Move ownership to `self.small`
let memory = shrink(
&mut self.large,
&mut self.small,
ptr,
layout,
new_size,
placement,
)?;
Ok(self.clamp_memory(memory))
} else {
self.large.shrink(ptr, layout, new_size, placement)
}
}
} This needs more testing before I can push this. |
@TimDiekmann Well, from my understanding, though I may have missed something, just having two generics with For example, a stack allocator must stay in a specific location, so the part that implements Implementing As for the unsoundness, I'm not surprised. I wrote it in about an hour and wasn't paying too much attention to details. |
You are right, that you must not implement Implementing your proposal makes sense anyway as |
Yes, you can define an Allocator that way, but it's certainly nice to be able to store everything inline if you want. let allocator = FallBack<StackAlloc<N>, Global>::default(); There may also be cache-locality benefits from making the allocator state very compact and inline. |
That would require
I don't see how this would be more efficient than let mut stack_alloc = StackAlloc::new();
let alloc = FallBack {
primary: &mut stack_alloc,
fallback: Global
}; Actually, |
Well, I actually implemented it in my example, so it's definitely possible. Default is just only implemented when the generic parameters have
Well, for stack allocator it's probably not. But with more complex allocators keeping all the state in single struct is definitely more efficient both because pointer chasing can add overhead and also because being able to fit the entire allocator in a few cache lines as possible can really speed things up.
Sure, but if you want to be able to take both |
// Either derive Default
#[derive(Default)]
struct TakesRef<'a, T> {
t: &'a mut T,
// ^^^^^^^^^ the trait `std::default::Default` is not implemented for `&mut T`
}
// Or implement it manually
impl<T: Default> Default for TakesRef<'_, T> {
fn default() -> Self {
Self {
t: &mut T::default()
// ------------ temporary value created here
}
}
}
}
I still don't see, why you want to force the user to use
Then you just pass |
My whole thing is about giving the user choice here. Here's the #[derive(Default)]
pub struct Fallback<Primary, Secondary> {
primary: Primary,
secondary: Secondary,
}
unsafe impl<Primary, Secondary> AllocRef for &'_ mut Fallback<Primary, Secondary>
where
for<'a> &'a mut Primary: AllocRef,
for<'a> &'a Primary: AllocOwner,
for<'a> &'a mut Secondary: AllocRef,
{
// ...
} If you require that You can use this either way! let alloc = Fallback::<StackAlloc<N>, Global>::default() or let mut stack = StackAlloc<N>::default();
let alloc: Fallback<&mut StackAlloc<N>, Global> = Fallback::new(&mut stack, Global); This gives the user choice to do either. |
Then I still don't see, why |
I think this is pretty offtopic here, we may move the discussion to your gist or to alloc-compose instead 🙂 |
…nieu Add a "by reference" adaptor for `AllocRef` Fixes rust-lang/wg-allocators#53 r? @Amanieu
For usability purposes, I'm proposing that the following implementation be added:
Without this implementation, I believe it's impossible to implement composable allocators, as I have started doing in this gist: https://gist.github.com/lachlansneff/8f59278bccb82ec5b36c359f385b7e3f.
The text was updated successfully, but these errors were encountered: