Allocator traits and std::heap #32838

Open
nikomatsakis opened this Issue Apr 8, 2016 · 375 comments

Comments

Projects
None yet
@nikomatsakis
Contributor

nikomatsakis commented Apr 8, 2016

Tracking issue for rust-lang/rfcs#1398 and the std::heap module.

  • land struct Layout, trait Allocator, and default implementations in alloc crate (#42313)
  • decide where parts should live (e.g. default impls has dependency on alloc crate, but Layout/Allocator could be in libcore...) (#42313)
  • fixme from source code: audit default implementations (in Layout for overflow errors, (potentially switching to overflowing_add and overflowing_mul as necessary).
  • decide if realloc_in_place should be replaced with grow_in_place and shrink_in_place (comment) (#42313)
  • review arguments for/against associated error type (see subthread here)
  • determine what the requirements are on the alignment provided to fn dealloc. (See discussion on allocator rfc and global allocator rfc and trait Alloc PR.)
    • Is it required to deallocate with the exact align that you allocate with? Concerns have been raised that allocators like jemalloc don't require this, and it's difficult to envision an allocator that does require this. (more discussion). @ruuda and @rkruppe look like they've got the most thoughts so far on this.
  • should AllocErr be Error instead? (comment)
  • Is it required to deallocate with the exact size that you allocate with? With the usable_size business we may wish to allow, for example, that you if you allocate with (size, align) you must deallocate with a size somewhere in the range of size...usable_size(size, align). It appears that jemalloc is totally ok with this (doesn't require you to deallocate with a precise size you allocate with) and this would also allow Vec to naturally take advantage of the excess capacity jemalloc gives it when it does an allocation. (although actually doing this is also somewhat orthogonal to this decision, we're just empowering Vec). So far @Gankro has most of the thoughts on this. (@alexcrichton believes this was settled in #42313 due to the definition of "fits")
  • similar to previous question: Is it required to deallocate with the exact alignment that you allocated with? (See comment from 5 June 2017)
  • OSX/alloc_system is buggy on huge alignments (e.g. an align of 1 << 32) #30170 #43217
  • should Layout provide a fn stride(&self) method? (See also rust-lang/rfcs#1397, #17027 )
  • Allocator::owns as a method? #44302

State of std::heap after #42313:

pub struct Layout { /* ... */ }

impl Layout {
    pub fn new<T>() -> Self;
    pub fn for_value<T: ?Sized>(t: &T) -> Self;
    pub fn array<T>(n: usize) -> Option<Self>;
    pub fn from_size_align(size: usize, align: usize) -> Option<Layout>;
    pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Layout;

    pub fn size(&self) -> usize;
    pub fn align(&self) -> usize;
    pub fn align_to(&self, align: usize) -> Self;
    pub fn padding_needed_for(&self, align: usize) -> usize;
    pub fn repeat(&self, n: usize) -> Option<(Self, usize)>;
    pub fn extend(&self, next: Self) -> Option<(Self, usize)>;
    pub fn repeat_packed(&self, n: usize) -> Option<Self>;
    pub fn extend_packed(&self, next: Self) -> Option<(Self, usize)>;
}

pub enum AllocErr {
    Exhausted { request: Layout },
    Unsupported { details: &'static str },
}

impl AllocErr {
    pub fn invalid_input(details: &'static str) -> Self;
    pub fn is_memory_exhausted(&self) -> bool;
    pub fn is_request_unsupported(&self) -> bool;
    pub fn description(&self) -> &str;
}

pub struct CannotReallocInPlace;

pub struct Excess(pub *mut u8, pub usize);

pub unsafe trait Alloc {
    // required
    unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout);

    // provided
    fn oom(&mut self, _: AllocErr) -> !;
    fn usable_size(&self, layout: &Layout) -> (usize, usize);
    unsafe fn realloc(&mut self,
                      ptr: *mut u8,
                      layout: Layout,
                      new_layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr>;
    unsafe fn realloc_excess(&mut self,
                             ptr: *mut u8,
                             layout: Layout,
                             new_layout: Layout) -> Result<Excess, AllocErr>;
    unsafe fn grow_in_place(&mut self,
                            ptr: *mut u8,
                            layout: Layout,
                            new_layout: Layout) -> Result<(), CannotReallocInPlace>;
    unsafe fn shrink_in_place(&mut self,
                              ptr: *mut u8,
                              layout: Layout,
                              new_layout: Layout) -> Result<(), CannotReallocInPlace>;

    // convenience
    fn alloc_one<T>(&mut self) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn dealloc_one<T>(&mut self, ptr: Unique<T>)
        where Self: Sized;
    fn alloc_array<T>(&mut self, n: usize) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn realloc_array<T>(&mut self,
                               ptr: Unique<T>,
                               n_old: usize,
                               n_new: usize) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn dealloc_array<T>(&mut self, ptr: Unique<T>, n: usize) -> Result<(), AllocErr>
        where Self: Sized;
}

/// The global default allocator
pub struct Heap;

impl Alloc for Heap {
    // ...
}

impl<'a> Alloc for &'a Heap {
    // ...
}

/// The "system" allocator
pub struct System;

impl Alloc for System {
    // ...
}

impl<'a> Alloc for &'a System {
    // ...
}

@nikomatsakis nikomatsakis referenced this issue in rust-lang/rfcs Apr 8, 2016

Merged

Allocators, take III #1398

12 of 25 tasks complete
@gereeter

This comment has been minimized.

Show comment
Hide comment
@gereeter

gereeter Apr 11, 2016

Contributor

I unfortunately wasn't paying close enough attention to mention this in the RFC discussion, but I think that realloc_in_place should be replaced by two functions, grow_in_place and shrink_in_place, for two reasons:

  • I can't think of a single use case (short of implementing realloc or realloc_in_place) where it is unknown whether the size of the allocation is increasing or decreasing. Using more specialized methods makes it slightly more clear what is going on.
  • The code paths for growing and shrinking allocations tend to be radically different - growing involves testing whether adjacent blocks of memory are free and claiming them, while shrinking involves carving off properly sized subblocks and freeing them. While the cost of a branch inside realloc_in_place is quite small, using grow and shrink better captures the distinct tasks that an allocator needs to perform.

Note that these can be added backwards-compatibly next to realloc_in_place, but this would constrain which functions would be by default implemented in terms of which others.

For consistency, realloc would probably also want to be split into grow and split, but the only advantage to having an overloadable realloc function that I know of is to be able to use mmap's remap option, which does not have such a distinction.

Contributor

gereeter commented Apr 11, 2016

I unfortunately wasn't paying close enough attention to mention this in the RFC discussion, but I think that realloc_in_place should be replaced by two functions, grow_in_place and shrink_in_place, for two reasons:

  • I can't think of a single use case (short of implementing realloc or realloc_in_place) where it is unknown whether the size of the allocation is increasing or decreasing. Using more specialized methods makes it slightly more clear what is going on.
  • The code paths for growing and shrinking allocations tend to be radically different - growing involves testing whether adjacent blocks of memory are free and claiming them, while shrinking involves carving off properly sized subblocks and freeing them. While the cost of a branch inside realloc_in_place is quite small, using grow and shrink better captures the distinct tasks that an allocator needs to perform.

Note that these can be added backwards-compatibly next to realloc_in_place, but this would constrain which functions would be by default implemented in terms of which others.

For consistency, realloc would probably also want to be split into grow and split, but the only advantage to having an overloadable realloc function that I know of is to be able to use mmap's remap option, which does not have such a distinction.

@gereeter

This comment has been minimized.

Show comment
Hide comment
@gereeter

gereeter Apr 11, 2016

Contributor

Additionally, I think that the default implementations of realloc and realloc_in_place should be slightly adjusted - instead of checking against the usable_size, realloc should just first try to realloc_in_place. In turn, realloc_in_place should by default check against the usable size and return success in the case of a small change instead of universally returning failure.

This makes it easier to produce a high-performance implementation of realloc: all that is required is improving realloc_in_place. However, the default performance of realloc does not suffer, as the check against the usable_size is still performed.

Contributor

gereeter commented Apr 11, 2016

Additionally, I think that the default implementations of realloc and realloc_in_place should be slightly adjusted - instead of checking against the usable_size, realloc should just first try to realloc_in_place. In turn, realloc_in_place should by default check against the usable size and return success in the case of a small change instead of universally returning failure.

This makes it easier to produce a high-performance implementation of realloc: all that is required is improving realloc_in_place. However, the default performance of realloc does not suffer, as the check against the usable_size is still performed.

eddyb added a commit to eddyb/rust that referenced this issue Oct 18, 2016

Rollup merge of #37117 - pnkfelix:may-dangle-attr, r=nikomatsakis
`#[may_dangle]` attribute

`#[may_dangle]` attribute

Second step of #34761. Last big hurdle before we can work in earnest towards Allocator integration (#32838)

Note: I am not clear if this is *also* a syntax-breaking change that needs to be part of a breaking-batch.

eddyb added a commit to eddyb/rust that referenced this issue Oct 19, 2016

Rollup merge of #37117 - pnkfelix:may-dangle-attr, r=nikomatsakis
`#[may_dangle]` attribute

`#[may_dangle]` attribute

Second step of #34761. Last big hurdle before we can work in earnest towards Allocator integration (#32838)

Note: I am not clear if this is *also* a syntax-breaking change that needs to be part of a breaking-batch.

eddyb added a commit to eddyb/rust that referenced this issue Oct 19, 2016

Rollup merge of #37117 - pnkfelix:may-dangle-attr, r=nikomatsakis
`#[may_dangle]` attribute

`#[may_dangle]` attribute

Second step of #34761. Last big hurdle before we can work in earnest towards Allocator integration (#32838)

Note: I am not clear if this is *also* a syntax-breaking change that needs to be part of a breaking-batch.
@pnkfelix

This comment has been minimized.

Show comment
Hide comment
@pnkfelix

pnkfelix Oct 26, 2016

Member

Another issue: The doc for fn realloc_in_place says that if it returns Ok, then one is assured that ptr now "fits" new_layout.

To me this implies that it must check that the alignment of the given address matches any constraint implied by new_layout.

However, I don't think the spec for the underlying fn reallocate_inplace function implies that it will perform any such check.

  • Furthermore, it seems reasonable that any client diving into using fn realloc_in_place will themselves be ensuring that the alignments work (in practice I suspect it means that the same alignment is required everywhere for the given use case...)

So, should the implementation of fn realloc_in_place really be burdened with checking that the alignment of the given ptr is compatible with that of new_layout? It is probably better in this case (of this one method) to push that requirement back to the caller...

Member

pnkfelix commented Oct 26, 2016

Another issue: The doc for fn realloc_in_place says that if it returns Ok, then one is assured that ptr now "fits" new_layout.

To me this implies that it must check that the alignment of the given address matches any constraint implied by new_layout.

However, I don't think the spec for the underlying fn reallocate_inplace function implies that it will perform any such check.

  • Furthermore, it seems reasonable that any client diving into using fn realloc_in_place will themselves be ensuring that the alignments work (in practice I suspect it means that the same alignment is required everywhere for the given use case...)

So, should the implementation of fn realloc_in_place really be burdened with checking that the alignment of the given ptr is compatible with that of new_layout? It is probably better in this case (of this one method) to push that requirement back to the caller...

@pnkfelix

This comment has been minimized.

Show comment
Hide comment
@pnkfelix

pnkfelix Oct 26, 2016

Member

@gereeter you make good points; I will add them to the check list I am accumulating in the issue description.

Member

pnkfelix commented Oct 26, 2016

@gereeter you make good points; I will add them to the check list I am accumulating in the issue description.

@pnkfelix

This comment has been minimized.

Show comment
Hide comment
@pnkfelix

pnkfelix Oct 31, 2016

Member

(at this point I am waiting for #[may_dangle] support to ride the train into the beta channel so that I will then be able to use it for std collections as part of allocator integration)

Member

pnkfelix commented Oct 31, 2016

(at this point I am waiting for #[may_dangle] support to ride the train into the beta channel so that I will then be able to use it for std collections as part of allocator integration)

@Ixrec Ixrec referenced this issue in rust-lang/rfcs Dec 10, 2016

Closed

Alloca for Rust #1808

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

I'm new to Rust, so forgive me if this has been discussed elsewhere.

Is there any thought on how to support object-specific allocators? Some allocators such as slab allocators and magazine allocators are bound to a particular type, and do the work of constructing new objects, caching constructed objects which have been "freed" (rather than actually dropping them), returning already-constructed cached objects, and dropping objects before freeing the underlying memory to an underlying allocator when required.

Currently, this proposal doesn't include anything along the lines of ObjectAllocator<T>, but it would be very helpful. In particular, I'm working on an implementation of a magazine allocator object-caching layer (link above), and while I can have this only wrap an Allocator and do the work of constructing and dropping objects in the caching layer itself, it'd be great if I could also have this wrap other object allocators (like a slab allocator) and truly be a generic caching layer.

Where would an object allocator type or trait fit into this proposal? Would it be left for a future RFC? Something else?

Contributor

joshlf commented Jan 4, 2017

I'm new to Rust, so forgive me if this has been discussed elsewhere.

Is there any thought on how to support object-specific allocators? Some allocators such as slab allocators and magazine allocators are bound to a particular type, and do the work of constructing new objects, caching constructed objects which have been "freed" (rather than actually dropping them), returning already-constructed cached objects, and dropping objects before freeing the underlying memory to an underlying allocator when required.

Currently, this proposal doesn't include anything along the lines of ObjectAllocator<T>, but it would be very helpful. In particular, I'm working on an implementation of a magazine allocator object-caching layer (link above), and while I can have this only wrap an Allocator and do the work of constructing and dropping objects in the caching layer itself, it'd be great if I could also have this wrap other object allocators (like a slab allocator) and truly be a generic caching layer.

Where would an object allocator type or trait fit into this proposal? Would it be left for a future RFC? Something else?

@Ericson2314

This comment has been minimized.

Show comment
Hide comment
@Ericson2314

Ericson2314 Jan 4, 2017

Contributor

I don't think this has been discussed yet.

You could write your own ObjectAllocator<T>, and then do impl<T: Allocator, U> ObjectAllocator<U> for T { .. }, so that every regular allocator can serve as an object-specific allocator for all objects.

Future work would be modifying collections to use your trait for their nodes, instead of plain ole' (generic) allocators directly.

Contributor

Ericson2314 commented Jan 4, 2017

I don't think this has been discussed yet.

You could write your own ObjectAllocator<T>, and then do impl<T: Allocator, U> ObjectAllocator<U> for T { .. }, so that every regular allocator can serve as an object-specific allocator for all objects.

Future work would be modifying collections to use your trait for their nodes, instead of plain ole' (generic) allocators directly.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Jan 4, 2017

Contributor

@pnkfelix

(at this point I am waiting for #[may_dangle] support to ride the train into the beta channel so that I will then be able to use it for std collections as part of allocator integration)

I guess this has happened?

Contributor

nikomatsakis commented Jan 4, 2017

@pnkfelix

(at this point I am waiting for #[may_dangle] support to ride the train into the beta channel so that I will then be able to use it for std collections as part of allocator integration)

I guess this has happened?

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

@Ericson2314 Yeah, writing my own is definitely an option for experimental purposes, but I think there'd be much more benefit to it being standardized in terms of interoperability (for example, I plan on also implementing a slab allocator, but it would be nice if a third-party user of my code could use somebody else's slab allocator with my magazine caching layer). My question is simply whether an ObjectAllocator<T> trait or something like it is worth discussing. Although it seems that it might be best for a different RFC? I'm not terribly familiar with the guidelines for how much belongs in a single RFC and when things belong in separate RFCs...

Contributor

joshlf commented Jan 4, 2017

@Ericson2314 Yeah, writing my own is definitely an option for experimental purposes, but I think there'd be much more benefit to it being standardized in terms of interoperability (for example, I plan on also implementing a slab allocator, but it would be nice if a third-party user of my code could use somebody else's slab allocator with my magazine caching layer). My question is simply whether an ObjectAllocator<T> trait or something like it is worth discussing. Although it seems that it might be best for a different RFC? I'm not terribly familiar with the guidelines for how much belongs in a single RFC and when things belong in separate RFCs...

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Jan 4, 2017

Member

@joshlf

Where would an object allocator type or trait fit into this proposal? Would it be left for a future RFC? Something else?

Yes, it would be another RFC.

I'm not terribly familiar with the guidelines for how much belongs in a single RFC and when things belong in separate RFCs...

that depends on the scope of the RFC itself, which is decided by the person who writes it, and then feedback is given by everyone.

But really, as this is a tracking issue for this already-accepted RFC, thinking about extensions and design changes isn't really for this thread; you should open a new one over on the RFCs repo.

Member

steveklabnik commented Jan 4, 2017

@joshlf

Where would an object allocator type or trait fit into this proposal? Would it be left for a future RFC? Something else?

Yes, it would be another RFC.

I'm not terribly familiar with the guidelines for how much belongs in a single RFC and when things belong in separate RFCs...

that depends on the scope of the RFC itself, which is decided by the person who writes it, and then feedback is given by everyone.

But really, as this is a tracking issue for this already-accepted RFC, thinking about extensions and design changes isn't really for this thread; you should open a new one over on the RFCs repo.

@Ericson2314

This comment has been minimized.

Show comment
Hide comment
@Ericson2314

Ericson2314 Jan 4, 2017

Contributor

@joshlf Ah, I thought ObjectAllocator<T> was supposed to be a trait. I meant prototype the trait not a specific allocator. Yes that trait would merit its own RFC as @steveklabnik says.


@steveklabnik yeah now discussion would be better elsewhere. But @joshlf was also raising the issue lest it expose a hitherto unforeseen flaw in the accepted but unimplemented API design. In that sense it matches the earlier posts in this thread.

Contributor

Ericson2314 commented Jan 4, 2017

@joshlf Ah, I thought ObjectAllocator<T> was supposed to be a trait. I meant prototype the trait not a specific allocator. Yes that trait would merit its own RFC as @steveklabnik says.


@steveklabnik yeah now discussion would be better elsewhere. But @joshlf was also raising the issue lest it expose a hitherto unforeseen flaw in the accepted but unimplemented API design. In that sense it matches the earlier posts in this thread.

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

@Ericson2314 Yeah, I thought that was what you meant. I think we're on the same page :)

@steveklabnik Sounds good; I'll poke around with my own implementation and submit an RFC if it ends up seeming like a good idea.

Contributor

joshlf commented Jan 4, 2017

@Ericson2314 Yeah, I thought that was what you meant. I think we're on the same page :)

@steveklabnik Sounds good; I'll poke around with my own implementation and submit an RFC if it ends up seeming like a good idea.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 4, 2017

Contributor

@joshlf I don't any reason why custom allocators would go into the compiler or standard library. Once this RFC lands, you could easily publish your own crate that does an arbitrary sort of allocation (even a fully-fledged allocator like jemalloc could be custom-implemented!).

Contributor

alexreg commented Jan 4, 2017

@joshlf I don't any reason why custom allocators would go into the compiler or standard library. Once this RFC lands, you could easily publish your own crate that does an arbitrary sort of allocation (even a fully-fledged allocator like jemalloc could be custom-implemented!).

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

@alexreg This isn't about a particular custom allocator, but rather a trait that specifies the type of all allocators which are parametric on a particular type. So just like RFC 1398 defines a trait (Allocator) that is the type of any low-level allocator, I'm asking about a trait (ObjectAllocator<T>) that is the type of any allocator which can allocate/deallocate and construct/drop objects of type T.

Contributor

joshlf commented Jan 4, 2017

@alexreg This isn't about a particular custom allocator, but rather a trait that specifies the type of all allocators which are parametric on a particular type. So just like RFC 1398 defines a trait (Allocator) that is the type of any low-level allocator, I'm asking about a trait (ObjectAllocator<T>) that is the type of any allocator which can allocate/deallocate and construct/drop objects of type T.

@Ericson2314

This comment has been minimized.

Show comment
Hide comment
@Ericson2314

Ericson2314 Jan 4, 2017

Contributor

@alexreg See my early point about using standard library collections with custom object-specific allocators.

Contributor

Ericson2314 commented Jan 4, 2017

@alexreg See my early point about using standard library collections with custom object-specific allocators.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 4, 2017

Contributor
Contributor

alexreg commented Jan 4, 2017

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 4, 2017

Contributor
Contributor

alexreg commented Jan 4, 2017

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

Sure, but I’m not sure that would belong in the standard library. Could easily go into another crate, with no loss of functionality or usability.

Yes but you probably want some standard library functionality to rely on it (such as what @Ericson2314 suggested).

I think you’d want to use standard-library collections (any heap-allocated value) with an arbitrary custom allocator; i.e. not limited to object-specific ones.

Ideally you'd want both - to accept either type of allocator. There are very significant benefits to using object-specific caching; for example, both slab allocation and magazine caching give very significant performance benefits - take a look at the papers I linked to above if you're curious.

Contributor

joshlf commented Jan 4, 2017

Sure, but I’m not sure that would belong in the standard library. Could easily go into another crate, with no loss of functionality or usability.

Yes but you probably want some standard library functionality to rely on it (such as what @Ericson2314 suggested).

I think you’d want to use standard-library collections (any heap-allocated value) with an arbitrary custom allocator; i.e. not limited to object-specific ones.

Ideally you'd want both - to accept either type of allocator. There are very significant benefits to using object-specific caching; for example, both slab allocation and magazine caching give very significant performance benefits - take a look at the papers I linked to above if you're curious.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 4, 2017

Contributor
Contributor

alexreg commented Jan 4, 2017

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 4, 2017

Contributor

But the object allocator trait could simply be a subtrait of the general allocator trait. It’s as simple as that, as far as I’m concerned. Sure, certain types of allocators can be more efficient than general-purpose allocators, but neither the compiler nor the standard really need to (or indeed should) know about this.

Ah, so the problem is that the semantics are different. Allocator allocates and frees raw byte blobs. ObjectAllocator<T>, on the other hand, would allocate already-constructed objects and would also be responsible for dropping these objects (including being able to cache constructed objects which could be handed out later in leu of constructing a newly-allocated object, which is expensive). The trait would look something like this:

trait ObjectAllocator<T> {
    fn alloc() -> T;
    fn free(t T);
}

This is not compatible with Allocator, whose methods deal with raw pointers and have no notion of type. Additionally, with Allocators, it is the caller's responsibility to drop the object being freed first. This is really important - knowing about the type T allows ObjectAllocator<T> to do things like call T's drop method, and since free(t) moves t into free, the caller cannot drop t first - it is instead the ObjectAllocator<T>'s responsibility. Fundamentally, these two traits are incompatible with one another.

Contributor

joshlf commented Jan 4, 2017

But the object allocator trait could simply be a subtrait of the general allocator trait. It’s as simple as that, as far as I’m concerned. Sure, certain types of allocators can be more efficient than general-purpose allocators, but neither the compiler nor the standard really need to (or indeed should) know about this.

Ah, so the problem is that the semantics are different. Allocator allocates and frees raw byte blobs. ObjectAllocator<T>, on the other hand, would allocate already-constructed objects and would also be responsible for dropping these objects (including being able to cache constructed objects which could be handed out later in leu of constructing a newly-allocated object, which is expensive). The trait would look something like this:

trait ObjectAllocator<T> {
    fn alloc() -> T;
    fn free(t T);
}

This is not compatible with Allocator, whose methods deal with raw pointers and have no notion of type. Additionally, with Allocators, it is the caller's responsibility to drop the object being freed first. This is really important - knowing about the type T allows ObjectAllocator<T> to do things like call T's drop method, and since free(t) moves t into free, the caller cannot drop t first - it is instead the ObjectAllocator<T>'s responsibility. Fundamentally, these two traits are incompatible with one another.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 5, 2017

Contributor
Contributor

alexreg commented Jan 5, 2017

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jan 5, 2017

Contributor

@alexreg Ah yes, I was hoping so too :) Oh well - it'll have to wait for another RFC.

Contributor

joshlf commented Jan 5, 2017

@alexreg Ah yes, I was hoping so too :) Oh well - it'll have to wait for another RFC.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jan 5, 2017

Contributor
Contributor

alexreg commented Jan 5, 2017

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Jan 12, 2017

A crate for testing custom allocators would be useful.

burdges commented Jan 12, 2017

A crate for testing custom allocators would be useful.

hawkw added a commit to sos-os/kernel that referenced this issue Feb 3, 2017

@hawkw

This comment has been minimized.

Show comment
Hide comment
@hawkw

hawkw Feb 7, 2017

Forgive me if I'm missing something obvious, but is there a reason for the Layout trait described in this RFC not to implement Copy as well as Clone, since it's just POD?

hawkw commented Feb 7, 2017

Forgive me if I'm missing something obvious, but is there a reason for the Layout trait described in this RFC not to implement Copy as well as Clone, since it's just POD?

@Ericson2314

This comment has been minimized.

Show comment
Hide comment
@Ericson2314

Ericson2314 Mar 20, 2017

Contributor

I can't think of any.

Contributor

Ericson2314 commented Mar 20, 2017

I can't think of any.

@chriskrycho chriskrycho referenced this issue in rust-lang-nursery/reference Mar 29, 2017

Closed

Document all features #9

18 of 48 tasks complete
@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium May 3, 2018

Contributor

There is one change to the API that I'm considering to submit a PR for

Did so in #50436

Contributor

glandium commented May 3, 2018

There is one change to the API that I'm considering to submit a PR for

Did so in #50436

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 4, 2018

Contributor

@glandium

(and as a matter of fact, I do have such a need for mozjemalloc),

Could you elaborate on this use case?

Contributor

gnzlbg commented May 4, 2018

@glandium

(and as a matter of fact, I do have such a need for mozjemalloc),

Could you elaborate on this use case?

@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium May 4, 2018

Contributor

mozjemalloc has a base allocator that purposefully leaks. Except for one kind of objects, where it keeps a free list. I can do that by layering allocators rather than do tricks with alloc_one.

Contributor

glandium commented May 4, 2018

mozjemalloc has a base allocator that purposefully leaks. Except for one kind of objects, where it keeps a free list. I can do that by layering allocators rather than do tricks with alloc_one.

@retep998

This comment has been minimized.

Show comment
Hide comment
@retep998

retep998 May 7, 2018

Member

Is it required to deallocate with the exact alignment that you allocated with?

Just to reinforce that the answer to this question is YES, I have this lovely quote from Microsoft themselves:

aligned_alloc() will probably never be implemented, as C11 specified it in a way that’s incompatible with our implementation (namely, that free() must be able to handle highly aligned allocations)

Using the system allocator on Windows will always require knowing the alignment when deallocating in order to correctly deallocate highly aligned allocations, so can we please just mark that question as resolved?

Member

retep998 commented May 7, 2018

Is it required to deallocate with the exact alignment that you allocated with?

Just to reinforce that the answer to this question is YES, I have this lovely quote from Microsoft themselves:

aligned_alloc() will probably never be implemented, as C11 specified it in a way that’s incompatible with our implementation (namely, that free() must be able to handle highly aligned allocations)

Using the system allocator on Windows will always require knowing the alignment when deallocating in order to correctly deallocate highly aligned allocations, so can we please just mark that question as resolved?

@ruuda

This comment has been minimized.

Show comment
Hide comment
@ruuda

ruuda May 7, 2018

Contributor

Using the system allocator on Windows will always require knowing the alignment when deallocating in order to correctly deallocate highly aligned allocations, so can we please just mark that question as resolved?

It’s a shame, but it is the way it is. Let’s give up on overaligned vectors then. 😕

Contributor

ruuda commented May 7, 2018

Using the system allocator on Windows will always require knowing the alignment when deallocating in order to correctly deallocate highly aligned allocations, so can we please just mark that question as resolved?

It’s a shame, but it is the way it is. Let’s give up on overaligned vectors then. 😕

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 7, 2018

Contributor

Let’s give up on overaligned vectors then

How come? You just need Vec<T, OverAlignedAlloc<U16>> that both allocates and deallocates with overalignment.

Contributor

gnzlbg commented May 7, 2018

Let’s give up on overaligned vectors then

How come? You just need Vec<T, OverAlignedAlloc<U16>> that both allocates and deallocates with overalignment.

@ruuda

This comment has been minimized.

Show comment
Hide comment
@ruuda

ruuda May 7, 2018

Contributor

How come? You just need Vec<T, OverAlignedAlloc<U16>> that both allocates and deallocates with overalignment.

I should have been more specific. I meant moving overaligned vectors into an API outside of your control, i.e. one that takes a Vec<T> and not Vec<T, OverAlignedAlloc<U16>>. (For example CString::new().)

Contributor

ruuda commented May 7, 2018

How come? You just need Vec<T, OverAlignedAlloc<U16>> that both allocates and deallocates with overalignment.

I should have been more specific. I meant moving overaligned vectors into an API outside of your control, i.e. one that takes a Vec<T> and not Vec<T, OverAlignedAlloc<U16>>. (For example CString::new().)

@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium May 7, 2018

Contributor

You should rather use

#[repr(align(16))]
struct OverAligned16<T>(T);

and then Vec<OverAligned16<T>>.

Contributor

glandium commented May 7, 2018

You should rather use

#[repr(align(16))]
struct OverAligned16<T>(T);

and then Vec<OverAligned16<T>>.

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 7, 2018

Contributor

You should rather use

That depends. Suppose you want to use AVX intrinsics (256 bit wide, 32-byte alignment requirement) on a vector of f32s:

  • Vec<T, OverAlignedAlloc<U32>> solves the problem, one can use AVX intrinsics directly on the vector elements (in particular, aligned memory loads), and the vector still derefs into a &[f32] slice making it ergonomic to use.
  • Vec<OverAligned32<f32>> does not really solve the problem. Each f32 takes 32 bytes of space due to the alignment requirement. The padding introduced prevents the direct use of AVX operations since the f32s are not on continuous memory any more. And I personally find the deref to &[OverAligned32<f32>] a bit tedious to deal with.

For a single element in a Box, Box<T, OverAligned<U32>> vs Box<OverAligned32<T>>, both approaches are more equivalent, and the second approach might indeed be preferable. In any case is nice to have both options.

Contributor

gnzlbg commented May 7, 2018

You should rather use

That depends. Suppose you want to use AVX intrinsics (256 bit wide, 32-byte alignment requirement) on a vector of f32s:

  • Vec<T, OverAlignedAlloc<U32>> solves the problem, one can use AVX intrinsics directly on the vector elements (in particular, aligned memory loads), and the vector still derefs into a &[f32] slice making it ergonomic to use.
  • Vec<OverAligned32<f32>> does not really solve the problem. Each f32 takes 32 bytes of space due to the alignment requirement. The padding introduced prevents the direct use of AVX operations since the f32s are not on continuous memory any more. And I personally find the deref to &[OverAligned32<f32>] a bit tedious to deal with.

For a single element in a Box, Box<T, OverAligned<U32>> vs Box<OverAligned32<T>>, both approaches are more equivalent, and the second approach might indeed be preferable. In any case is nice to have both options.

@glandium

This comment has been minimized.

Show comment
Hide comment
Contributor

glandium commented May 10, 2018

@Amanieu

This comment has been minimized.

Show comment
Hide comment
@Amanieu

Amanieu May 29, 2018

Contributor

The tracking post at the top of this issue is horribly out of date (was last edited in 2016). We need an updated list of active concerns to continue the discussion productively.

Contributor

Amanieu commented May 29, 2018

The tracking post at the top of this issue is horribly out of date (was last edited in 2016). We need an updated list of active concerns to continue the discussion productively.

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 29, 2018

Contributor

The discussion would also significantly benefit from an up-to-date design document, containing the current unresolved questions, and the rationale for the design decisions.

There are multiple threads of diffs from "what's currently implemented on nightly" to "what was proposed in the original Alloc RFC" spawning thousands of comments on different channels (rfc repo, rust-lang tracking issue, global alloc RFC, internal posts, many huge PRs, etc.), and what's being stabilized in the GlobalAlloc RFC does not look that much from what was proposed in the original RFC.

This is something that we need anyways to finish updating the docs and the reference, and would be helpful in the current discussions as well.

Contributor

gnzlbg commented May 29, 2018

The discussion would also significantly benefit from an up-to-date design document, containing the current unresolved questions, and the rationale for the design decisions.

There are multiple threads of diffs from "what's currently implemented on nightly" to "what was proposed in the original Alloc RFC" spawning thousands of comments on different channels (rfc repo, rust-lang tracking issue, global alloc RFC, internal posts, many huge PRs, etc.), and what's being stabilized in the GlobalAlloc RFC does not look that much from what was proposed in the original RFC.

This is something that we need anyways to finish updating the docs and the reference, and would be helpful in the current discussions as well.

@Amanieu

This comment has been minimized.

Show comment
Hide comment
@Amanieu

Amanieu May 29, 2018

Contributor

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

Contributor

Amanieu commented May 29, 2018

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf May 29, 2018

Contributor

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

Yes, absolutely. Especially Box, since we don't yet know how to avoid having Box<T, A> take up two words.

Contributor

joshlf commented May 29, 2018

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

Yes, absolutely. Especially Box, since we don't yet know how to avoid having Box<T, A> take up two words.

@Amanieu

This comment has been minimized.

Show comment
Hide comment
@Amanieu

Amanieu May 29, 2018

Contributor

Yes, absolutely. Especially Box, since we don't yet know how to avoid having Box<T, A> take up two words.

I don't think we should worry the size of Box<T, A> for the initial implementation, but this is something that can be added later in a backward-compatible way by adding a DeAlloc trait which only supports deallocation.

Example:

trait DeAlloc {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout);
}

trait Alloc {
    // In addition to the existing trait items
    type DeAlloc: DeAlloc = Self;
    fn into_dealloc(self) -> Self::DeAlloc {
        self
    }
}

impl<T: Alloc> DeAlloc for T {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout) {
        Alloc::dealloc(self, ptr, layout);
    }
}
Contributor

Amanieu commented May 29, 2018

Yes, absolutely. Especially Box, since we don't yet know how to avoid having Box<T, A> take up two words.

I don't think we should worry the size of Box<T, A> for the initial implementation, but this is something that can be added later in a backward-compatible way by adding a DeAlloc trait which only supports deallocation.

Example:

trait DeAlloc {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout);
}

trait Alloc {
    // In addition to the existing trait items
    type DeAlloc: DeAlloc = Self;
    fn into_dealloc(self) -> Self::DeAlloc {
        self
    }
}

impl<T: Alloc> DeAlloc for T {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout) {
        Alloc::dealloc(self, ptr, layout);
    }
}
@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg May 29, 2018

Contributor

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

I think @Ericson2314 has been working on this, per #42774. Would be nice to get an update from him.

Contributor

alexreg commented May 29, 2018

I think that before we even think about stabilizing the Alloc trait, we should first try implementing allocator support in all of the standard library collections. This should give us some experience with how this trait will be used in practice.

I think @Ericson2314 has been working on this, per #42774. Would be nice to get an update from him.

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf May 29, 2018

Contributor

I don't think we should worry the size of Box<T, A> for the initial implementation, but this is something that can be added later in a backward-compatible way by adding a DeAlloc trait which only supports deallocation.

That's one approach, but it's not at all clear to me that it's definitely the best one. It has the distinct disadvantages, for example, that a) it only works when a pointer -> allocator lookup is possible (this isn't true of, e.g., most arena allocators) and, b) it adds significant overhead to dealloc (namely, to do the reverse lookup). It may end up being the case that the best solution to this problem is a more general-purpose effect or context system like this proposal or this proposal. Or perhaps something different altogether. So I don't think we should assume that this will be easy to solve in a manner which is backwards-compatible with the current incarnation of the Alloc trait.

Contributor

joshlf commented May 29, 2018

I don't think we should worry the size of Box<T, A> for the initial implementation, but this is something that can be added later in a backward-compatible way by adding a DeAlloc trait which only supports deallocation.

That's one approach, but it's not at all clear to me that it's definitely the best one. It has the distinct disadvantages, for example, that a) it only works when a pointer -> allocator lookup is possible (this isn't true of, e.g., most arena allocators) and, b) it adds significant overhead to dealloc (namely, to do the reverse lookup). It may end up being the case that the best solution to this problem is a more general-purpose effect or context system like this proposal or this proposal. Or perhaps something different altogether. So I don't think we should assume that this will be easy to solve in a manner which is backwards-compatible with the current incarnation of the Alloc trait.

@Amanieu

This comment has been minimized.

Show comment
Hide comment
@Amanieu

Amanieu May 29, 2018

Contributor

@joshlf Considering the fact that Box<T, A> only has access to itself when it is dropped, this is the best thing that we can do with safe code only. Such a pattern might be useful for arena-like allocators which have a no-op dealloc and just free memory when the allocator is dropped.

For more complicated systems where the allocator is owned by a container (e.g. LinkedList) and managed multiple allocations, I expect that Box will not be used internally. Instead, the LinkedList internals will use raw pointers which are allocated and freed with the Alloc instance that is contained in the LinkedList object. This will avoid doubling the size of every pointer.

Contributor

Amanieu commented May 29, 2018

@joshlf Considering the fact that Box<T, A> only has access to itself when it is dropped, this is the best thing that we can do with safe code only. Such a pattern might be useful for arena-like allocators which have a no-op dealloc and just free memory when the allocator is dropped.

For more complicated systems where the allocator is owned by a container (e.g. LinkedList) and managed multiple allocations, I expect that Box will not be used internally. Instead, the LinkedList internals will use raw pointers which are allocated and freed with the Alloc instance that is contained in the LinkedList object. This will avoid doubling the size of every pointer.

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf May 29, 2018

Contributor

Considering the fact that Box<T, A> only has access to itself when it is dropped, this is the best thing that we can do with safe code only. Such a pattern might be useful for arena-like allocators which have a no-op dealloc and just free memory when the allocator is dropped.

Right, but Box doesn't know that dealloc is no-op.

For more complicated systems where the allocator is owned by a container (e.g. LinkedList) and managed multiple allocations, I expect that Box will not be used internally. Instead, the LinkedList internals will use raw pointers which are allocated and freed with the Alloc instance that is contained in the LinkedList object. This will avoid doubling the size of every pointer.

I think it would really be a shame to require people to use unsafe code in order to write any collections at all. If the goal is to make all collections (presumably including those outside of the standard library) optionally parametric on an allocator, and Box isn't allocator-parametric, then a collections author must either not use Box at all or use unsafe code (and keep in mind that remembering to always free things is one of the most common types of memory unsafety in C and C++, so it's difficult-to-get-right unsafe code at that). That seems like an unfortunate bargain.

Contributor

joshlf commented May 29, 2018

Considering the fact that Box<T, A> only has access to itself when it is dropped, this is the best thing that we can do with safe code only. Such a pattern might be useful for arena-like allocators which have a no-op dealloc and just free memory when the allocator is dropped.

Right, but Box doesn't know that dealloc is no-op.

For more complicated systems where the allocator is owned by a container (e.g. LinkedList) and managed multiple allocations, I expect that Box will not be used internally. Instead, the LinkedList internals will use raw pointers which are allocated and freed with the Alloc instance that is contained in the LinkedList object. This will avoid doubling the size of every pointer.

I think it would really be a shame to require people to use unsafe code in order to write any collections at all. If the goal is to make all collections (presumably including those outside of the standard library) optionally parametric on an allocator, and Box isn't allocator-parametric, then a collections author must either not use Box at all or use unsafe code (and keep in mind that remembering to always free things is one of the most common types of memory unsafety in C and C++, so it's difficult-to-get-right unsafe code at that). That seems like an unfortunate bargain.

@eucpp

This comment has been minimized.

Show comment
Hide comment
@eucpp

eucpp Jun 12, 2018

Right, but Box doesn't know that dealloc is no-op.

Why wouldn't adapt what C++ unique_ptr does ?
That is: to store pointer to allocator if it's "stateful", and do not store it if the allocator is "stateless"
(e.g. global wrapper around malloc or mmap).
This would require to split current Alloc traint into two traits: StatefulAlloc and StatelessAlloc.
I realize that it is a very rude and inelegant (and probably someone has already proposed it in previous discussions).
Despite its inelegance this solution is simple and backward compatible (without performance penalties).

I think it would really be a shame to require people to use unsafe code in order to write any collections at all. If the goal is to make all collections (presumably including those outside of the standard library) optionally parametric on an allocator, and Box isn't allocator-parametric, then a collections author must either not use Box at all or use unsafe code (and keep in mind that remembering to always free things is one of the most common types of memory unsafety in C and C++, so it's difficult-to-get-right unsafe code at that). That seems like an unfortunate bargain.

I'm afraid that an implementation of effect or context system which could allow one to write node-based containers like lists, trees, etc in safe manner might take too much time (if it's possible in principle).
I didn't see any papers or academic languages that tackle this problem (please, correct me if such works actually exist).

So resorting to unsafe in implementation of node-based containers might be a necessary evil, at least in short-term perspective.

eucpp commented Jun 12, 2018

Right, but Box doesn't know that dealloc is no-op.

Why wouldn't adapt what C++ unique_ptr does ?
That is: to store pointer to allocator if it's "stateful", and do not store it if the allocator is "stateless"
(e.g. global wrapper around malloc or mmap).
This would require to split current Alloc traint into two traits: StatefulAlloc and StatelessAlloc.
I realize that it is a very rude and inelegant (and probably someone has already proposed it in previous discussions).
Despite its inelegance this solution is simple and backward compatible (without performance penalties).

I think it would really be a shame to require people to use unsafe code in order to write any collections at all. If the goal is to make all collections (presumably including those outside of the standard library) optionally parametric on an allocator, and Box isn't allocator-parametric, then a collections author must either not use Box at all or use unsafe code (and keep in mind that remembering to always free things is one of the most common types of memory unsafety in C and C++, so it's difficult-to-get-right unsafe code at that). That seems like an unfortunate bargain.

I'm afraid that an implementation of effect or context system which could allow one to write node-based containers like lists, trees, etc in safe manner might take too much time (if it's possible in principle).
I didn't see any papers or academic languages that tackle this problem (please, correct me if such works actually exist).

So resorting to unsafe in implementation of node-based containers might be a necessary evil, at least in short-term perspective.

@cramertj

This comment has been minimized.

Show comment
Hide comment
@cramertj

cramertj Jun 12, 2018

Member

@eucpp Note that unique_ptr doesn't store an allocator-- it stores a Deleter:

Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer`

I see this as roughly equivalent to us providing split Alloc and Dealloc traits.

Member

cramertj commented Jun 12, 2018

@eucpp Note that unique_ptr doesn't store an allocator-- it stores a Deleter:

Deleter must be FunctionObject or lvalue reference to a FunctionObject or lvalue reference to function, callable with an argument of type unique_ptr<T, Deleter>::pointer`

I see this as roughly equivalent to us providing split Alloc and Dealloc traits.

@eucpp

This comment has been minimized.

Show comment
Hide comment
@eucpp

eucpp Jun 12, 2018

@cramertj Yes, you are right. Still, two traits are required - stateful and stateless Dealloc.

eucpp commented Jun 12, 2018

@cramertj Yes, you are right. Still, two traits are required - stateful and stateless Dealloc.

@remexre

This comment has been minimized.

Show comment
Hide comment
@remexre

remexre Jun 12, 2018

Contributor
Contributor

remexre commented Jun 12, 2018

@eucpp

This comment has been minimized.

Show comment
Hide comment
@eucpp

eucpp Jun 12, 2018

Wouldn't a ZST Dealloc be sufficient?

@remexre I suppose it would :)

I didn't know that rust compiler supports ZST out of box.
In C++ it would require at least some tricks around empty base optimisation.
I'm pretty new at Rust so sorry for some obvious mistakes.

eucpp commented Jun 12, 2018

Wouldn't a ZST Dealloc be sufficient?

@remexre I suppose it would :)

I didn't know that rust compiler supports ZST out of box.
In C++ it would require at least some tricks around empty base optimisation.
I'm pretty new at Rust so sorry for some obvious mistakes.

@SimonSapin

This comment has been minimized.

Show comment
Hide comment
@SimonSapin

SimonSapin Jun 12, 2018

Contributor

I don’t think we need separate traits for stateful v.s. stateless.

With Box augmented with an A type parameter, it would contain a value of A directly, not a reference or pointer to A. That type can be zero-size fo a stateless (de)allocator. Or A itself can be something like a reference or handle to a stateful allocator that can be shared between multiple allocated objects. So instead of impl Alloc for MyAllocator, you might want to do something like impl<'r> Alloc for &'r MyAllocator

Contributor

SimonSapin commented Jun 12, 2018

I don’t think we need separate traits for stateful v.s. stateless.

With Box augmented with an A type parameter, it would contain a value of A directly, not a reference or pointer to A. That type can be zero-size fo a stateless (de)allocator. Or A itself can be something like a reference or handle to a stateful allocator that can be shared between multiple allocated objects. So instead of impl Alloc for MyAllocator, you might want to do something like impl<'r> Alloc for &'r MyAllocator

@SimonSapin

This comment has been minimized.

Show comment
Hide comment
@SimonSapin

SimonSapin Jun 12, 2018

Contributor

By the way, a Box that only knows how to deallocate and not how to allocate would not implement Clone.

Contributor

SimonSapin commented Jun 12, 2018

By the way, a Box that only knows how to deallocate and not how to allocate would not implement Clone.

@cramertj

This comment has been minimized.

Show comment
Hide comment
@cramertj

cramertj Jun 12, 2018

Member

@SimonSapin I'd expect that Cloneing would require specifying an allocator again, the same way that creating a new Box would (that is, it wouldn't be done using the Clone trait).

Member

cramertj commented Jun 12, 2018

@SimonSapin I'd expect that Cloneing would require specifying an allocator again, the same way that creating a new Box would (that is, it wouldn't be done using the Clone trait).

@eucpp

This comment has been minimized.

Show comment
Hide comment
@eucpp

eucpp Jun 13, 2018

@cramertj Wouldn't it be inconsistent compared to Vec and other containers that implement Clone ?
What are the downsides of storing instance of Alloc inside Box rather than Dealloc ?
Then Box might implement Clone as well as clone_with_alloc.

eucpp commented Jun 13, 2018

@cramertj Wouldn't it be inconsistent compared to Vec and other containers that implement Clone ?
What are the downsides of storing instance of Alloc inside Box rather than Dealloc ?
Then Box might implement Clone as well as clone_with_alloc.

@sfackler

This comment has been minimized.

Show comment
Hide comment
@sfackler

sfackler Jun 13, 2018

Member

I don't this the split traits really affect Clone in a huge way - the impl would just look like impl<T, A> Clone for Box<T, A> where A: Alloc + Dealloc + Clone { ... }.

Member

sfackler commented Jun 13, 2018

I don't this the split traits really affect Clone in a huge way - the impl would just look like impl<T, A> Clone for Box<T, A> where A: Alloc + Dealloc + Clone { ... }.

@cramertj

This comment has been minimized.

Show comment
Hide comment
@cramertj

cramertj Jun 13, 2018

Member

@sfackler I wouldn't be opposed to that impl, but I would also expect to have a clone_into or something that uses a provided allocator.

Member

cramertj commented Jun 13, 2018

@sfackler I wouldn't be opposed to that impl, but I would also expect to have a clone_into or something that uses a provided allocator.

@the8472

This comment has been minimized.

Show comment
Hide comment
@the8472

the8472 Jul 4, 2018

Would it make sense to a alloc_copy method to Alloc? This could be used to provide faster memcpy (Copy/Clone) implementations for large allocations, e.g. by doing copy-on-write clones of pages.

the8472 commented Jul 4, 2018

Would it make sense to a alloc_copy method to Alloc? This could be used to provide faster memcpy (Copy/Clone) implementations for large allocations, e.g. by doing copy-on-write clones of pages.

@joshlf

This comment has been minimized.

Show comment
Hide comment
@joshlf

joshlf Jul 4, 2018

Contributor

That would be pretty cool, and trivial to provide a default implementation for.

Contributor

joshlf commented Jul 4, 2018

That would be pretty cool, and trivial to provide a default implementation for.

@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium Jul 4, 2018

Contributor

What would be using such an alloc_copy function? impl Clone for Box<T, A>?

Contributor

glandium commented Jul 4, 2018

What would be using such an alloc_copy function? impl Clone for Box<T, A>?

@the8472

This comment has been minimized.

Show comment
Hide comment
@the8472

the8472 Jul 4, 2018

Yeah, ditto for Vec.

the8472 commented Jul 4, 2018

Yeah, ditto for Vec.

@the8472

This comment has been minimized.

Show comment
Hide comment
@the8472

the8472 Jul 5, 2018

Having looked into it some more it seems like approaches to create copy-on-write pages within the same process range between hacky and impossible, at least if you want to do it more than one level deep. So alloc_copy wouldn't be a huge benefit.

Instead a more general escape hatch that allows future virtual memory shenanigans might be of some use. I.e. if an allocation is large, backed by mmap anyway and stateless then the allocator could promise to be oblivious about future changes to the allocation. The user could then move that memory to a pipe, unmap it or similar things.
Alternatively there could be a dumb mmap-all-the-things allocator and a try-transfer function.

the8472 commented Jul 5, 2018

Having looked into it some more it seems like approaches to create copy-on-write pages within the same process range between hacky and impossible, at least if you want to do it more than one level deep. So alloc_copy wouldn't be a huge benefit.

Instead a more general escape hatch that allows future virtual memory shenanigans might be of some use. I.e. if an allocation is large, backed by mmap anyway and stateless then the allocator could promise to be oblivious about future changes to the allocation. The user could then move that memory to a pipe, unmap it or similar things.
Alternatively there could be a dumb mmap-all-the-things allocator and a try-transfer function.

@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg Jul 6, 2018

Contributor

Instead a more general escape hatch that allows future virtual memory

Memory allocators (malloc, jemalloc, ...) do not generally let you steal any kind of memory from them, and they do not generally let you query or change what the properties of the memory they own. So what does this general escape hatch has to do with memory allocators?

Also, virtual memory support differs greatly between platforms, so much that using virtual memory effectively often requires different algorithms per platform often with completely different guarantees. I have seen some portable abstractions over virtual memory, but I haven't seen one yet that wasn't crippled to the point of being useless in some situations due to their "portability".

Contributor

gnzlbg commented Jul 6, 2018

Instead a more general escape hatch that allows future virtual memory

Memory allocators (malloc, jemalloc, ...) do not generally let you steal any kind of memory from them, and they do not generally let you query or change what the properties of the memory they own. So what does this general escape hatch has to do with memory allocators?

Also, virtual memory support differs greatly between platforms, so much that using virtual memory effectively often requires different algorithms per platform often with completely different guarantees. I have seen some portable abstractions over virtual memory, but I haven't seen one yet that wasn't crippled to the point of being useless in some situations due to their "portability".

@the8472

This comment has been minimized.

Show comment
Hide comment
@the8472

the8472 Jul 6, 2018

You're right. Any such use-case (I was mostly thinking of platform-specific optimizations) is probably best served by using a custom allocator in the first place.

the8472 commented Jul 6, 2018

You're right. Any such use-case (I was mostly thinking of platform-specific optimizations) is probably best served by using a custom allocator in the first place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment