-
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
Box<T, A: BuildAlloc>
#12
Comments
So if I understand correctly, |
Almost exactly. The method signature is different, though, because the whole point is to give the allocator access to a pointer to its own heap memory from which to generate its Calling it |
I do have this very use-case, actually. And it didn't strike me that it could be solved this way, and I must say I like it. But there are details to figure out. Like... how do you get the (Note that last iteration in rust-lang/rust#58457 doesn't have bounds on the |
Aren’t bounds on the type definition required to allow |
This is exactly what I had in mind. Do you want to change the issue and the title to use |
Yes, the type signature for impl<T: ?Sized, A: BuildAlloc> Box<T, A> {
pub fn new_in(x: T, a: A::Alloc) -> Box<T, A>;
} And, as you point out, you need some way to create a I wasn't sure if such cyclic associated types are allowed, but it looks like they are. Edit: fix |
There's an interesting problem there... Drop currently does nothing. It's actually implemented by the compiler, which then goes on generating calls to |
Are there use cases where different allocations could have a different |
None that I can think of. That doesn't mean they don't exist, though. Allocators traditionally use a single global Ultimately, you can put whatever logic you want in |
How could |
I'm not sure why this is the case. If you put whatever you need in a |
But they're not really multiple independent allocators then, are they? They're one allocator that pretends to be multiple allocators. And there's an overhead associated with that. If the allocators are truly separate, then you could, for example, safely make them |
This is what I meant: struct A; // my allocator type
fn foo() {
static A0: A = A; // allocator 0
static A1: A = A; // allocator 1
#[derive(alloc_handle(A0))] struct AH0; // ZST handle to 0
// for exposition only, the derive expands to this:
impl Alloc for AH0 {
fn alloc(...) -> ... { A0.alloc(...) }
// ...
}
#[derive(alloc_handle(A1))] struct AH1; // ZST handle to 1
let b0: Box<T, AH0>; // box using A0
let b0: Box<T, AH0>; // another box using A0
let b1: Box<T, AH1>; // box using A1
// ^^ all boxes are pointer sized cause the handle is a ZST
} Whether you can have multiple handles to the same allocator or not, depends on your allocator impl. Whether you want to have one or multiple allocators, with different handles, is kind of up to you. If you can put your allocators in statics, or thread locals, then you can have zero-sized handles to them. If you cannot, then your handle needs to have some state to find the allocator, but I don't see any way around that. |
@gnzlbg This uses different Rust types for different handles, so there’s a bounded number of such types determined at compile-time. This is not what people mean by “multiple instances” of an allocator. For example: fn library_entry_point(some_input: Foo) {
let handle = ArenaAllocator::new();
// …
let b = Box::new_in(something, handle.clone());
let v = Vec::new_in(handle.clone());
// …
} A program that uses this library could spawn a large number of threads that each have their own separate arena. At compile time you don’t how many threads there’s gonna be, so you can have a Rust type for each of them. For |
@SimonSapin makes sense, I was misunderstanding which problem this solves, All fn build_alloc<T: ?Sized>(&self, heap_ptr: *const T) -> Self::Alloc;
// nitpick: heap_ptr does not necessarily point to the heap generic over unsafe fn build_alloc(&self, ptr: *const u8, layout: Layout) -> Self::Alloc; ? How can the proposed API be safe ? The OP mentions:
but Counter example, |
@gnzlbg You're right, it should be The reason I had proposed making the API safe is because the implementer must agree to ensure it is safe. For example, the implementer must not de-reference the heap_ptr (unless it somehow knows it can do so safely). However, we need to pass the |
I also can't think of any reason There might be some use cases where this could come in handy; maybe tracking the number of reallocations within the So I'll change that as well. |
@scottjmaddox You may take a look at https://github.com/TimDiekmann/alloc-wg? I have used |
I've just come across this issue. As it suggests adding a new function that makes it possible to have better control over how exactly the allocation is performed, would it be possible to take advantage of this method addition to make the method fallible, so that it's also possible to control how to behave when the allocation fails? (One example use case would be using an arena allocator for some untrusted data coming from the network, and wanting to abort just this specific client's instance if something OOMs here) |
Fallibility is a separate "axis" from using a provided allocator v.s. the global one. All four combinations are potentially useful. |
They are, but only the version that takes both an allocator and is fallible is required to be implement all the other ones: it's easy to do Now, if you want to make all four versions it's great too :) what I want to point out is just that if you plan on adding a single function for the time being, then it'd probably be better if it were the fallible one. (Also, I'm not sure we want to encourage the whole allocating ecosystem to provide 4 functions for each use case by setting that example in libstd) |
I should have mentioned the RFC https://rust-lang.github.io/rfcs/2116-alloc-me-maybe.html and its tracking issue rust-lang/rust#48043 for Yes |
I like to resolve the issues which suggest a change to To go further with this proposal, I think we need to work out an interface where each signature should be fully defined. Currently, it's a broad idea, of where to build I'll throw in some points here:
|
I have tested around a bit again with Some notes on the implementation:
|
I think we can defer // All AllocRef implement BuildAllocRef
trait AllocRef: BuildAllocRef {}
// We provide a blanket implementation of BuildAllocRef for all existing AllocRef
impl<T: AllocRef> BuildAllocRef for T {
// Just returns Self
}
// Relax the A bound to BuildAllocRef. This should be a backwards-compatible change.
struct Box<T, A: BuildAllocRef> {} |
The blanket implementation is a great idea! As no modifications to |
One thing that I feel hasn't been fully addressed is how you can construct a |
The current push to associate an allocator to boxes (among other types) is targeting something like
Box<T, A: Alloc>
. This limits the design space of custom allocators, though, because it forces one of two approaches for accessing theAlloc
:There are many kinds of allocators that these limitations rule out. For example, perhaps you want to have multiple independent "Arena" or "Region" allocators that each allocate from a dedicated slab of memory. Since you want to have multiple independent allocators, you cannot (efficiently) use approach 1. So you're forced to use approach 2, which is extremely inefficient, requiring a whole extra pointer to be stored in each
Box
.One approach for avoiding this limitation would be something like the following:
Note that
BuildAlloc
provides the methodbuild_alloc
, which accepts&mut self
and a pointer and layout for memory that was allocated by the associated allocator, and returns anAlloc
implementation. This gives many options for locating the allocator's state and generating anAlloc
. Approach 1 (global state) is of course still possible, and so is approach 2 (embed a pointer in every box). But this also makes approach 3 possible:This makes many more clever and efficient kinds of allocators possible, such as an allocator that allocates all 8-byte allocations from aligned 1 MiB blocks, and stashes a pointer to its own state at the beginning of each of those 1 MiB blocks. Such an allocator can implement
BuildAlloc
to check the size of the allocated memory and if it is 8-bytes then mask off the lowest 10 bits fromptr
to get a (1 MiB aligned) pointer to the allocator's state, from which it can construct anAlloc
.To clarify,
BuildAlloc
makes it possible to calculate a pointer to the allocator's state from theBox
itself:Since the
Box
provides a pointer to memory allocated by the allocator, the allocator can use its control of the pointers it returns and the memory around those pointers to efficiently locate its own state and generate anAlloc
. This makes many more kinds of clever and efficient allocators possible.Relevant comments from PR #58457: Associate an allocator to boxes:
AllocFrom
(old name ofBuildAlloc
) to have a method for each container type, when really we just need a single method that accepts a pointer to heap memoryEdited 2019-05-04: Rename
AllocFrom
toBuildAlloc
for consistency withBuildHasher
.Edited 2019-05-07: Adjust
build_alloc
signature to matchdealloc
.The text was updated successfully, but these errors were encountered: