-
Notifications
You must be signed in to change notification settings - Fork 13.4k
WIP: Add Faux allocator API #19320
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
WIP: Add Faux allocator API #19320
Conversation
cc me |
@pczarn looking at HashMap now, you only store the one pointer in the RawTable. Did you look at the relative perf of keeping the ptrs around? Would like the API to just return/take 3 ptrs. Not expose the details. |
// else UB anyway. | ||
let result_size = heap::reallocate_inplace(ptr as *mut u8, size * old_len, | ||
desired_size, align); | ||
if result_size == usable_size(desired_size, align) { |
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.
This will add a lot of overhead.
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.
Hmm, I was under the impression it was necessary to check. Or is it only for shrinks? If that's the case, it might make sense to split this into grow and shrink?
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.
Growth only requires a check for new_size >= requested_size
. The usable_size
check is only necessary to see if shrinking would have succeeded if heap::reallocate
had been used instead.
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 it reasonable to forbid new_len == old_len?
- Is it then possible to just check
new_size != old_size
unconditionally?
The docs seem unclear to me on how to evaluate if a shrink is successful, whatever that really means. Or is that just something we shouldn't care about? Like if a shrink fails "oh well"?
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 it reasonable to forbid new_len == old_len?
That's not going to work around the issue. There's a whole range of sizes corresponding to the same real size.
Is it then possible to just check new_size != old_size unconditionally?
No, because the returned size is the real size.
The docs seem unclear to me on how to evaluate if a shrink is successful, whatever that really means. Or is that just something we shouldn't care about? Like if a shrink fails "oh well"?
You can check for a shrink failure with the usable_size
check. I don't think it's a common use case.
This would significantly regress vector performance with all of the extra branches / code bloat so it's not going to be useful there. I don't think it makes sense for hash tables and ring buffers to use the low-level allocator API if they're going to be accepting needless overhead. It would make more sense to reuse |
How can HashMap reasonably use Vec to request the kind of buffer it wants? It wants a single buffer to store three different arrays of different kinds of elements. Also I don't think RingBuf should ultimately use Vec as it wants finer control over reallocation than Vec can provide (though it doesn't do this yet). |
Benches: On my crappy laptop (x64 windows in mingw):
No perf regression visible. Benches visible here, tested with Note that this is with the len == 0 check removed as per the version of faux in that branch. Feel free to suggest changes to my benches, or run it on your own hardware. |
Removed the |
EMPTY as *mut T | ||
} else { | ||
let ptr = heap::allocate(size, min_align_of::<T>()) as *mut T; | ||
if ptr == null_mut() { ::oom(); } |
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.
ptr.is_null()
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.
derp
LGTM. Although ideally I'd like to see that hashmap+btree integration instead of abstracting with only one consumer. |
I don't think it's going to work for |
I don't see why this should |
The fact that the higher level crates cannot be used in cases where robustness in the situation of memory exhaustion is required is not an excuse to ruin more of this crate too. It's a problem that should be addressed, not a good design pattern to emulate. |
The work as-is isn't enough for HashMap, but is a valuable starting point. @gankro is still working on a nice API for that use case. meta: "ruin" is a little melodramatic, don'tcha think? |
I don't think it's melodramatic to refer to objectively bad design that way. It's not acceptable to abort on runtime errors. In general, out-of-memory is a runtime error. There are many situations where it needs to be handled and this crate is intended to be used there. You can't write truly robust code with the higher level standard libraries. |
Simply aborting on a runtime error and then pointing out that the API is more convenient is lazy. There are two or three places in the standard libraries where it's going to be used, and explicitly aborting there is not a big deal. On the other hand, it makes the library totally inappropriate for use cases like a kernel or embedded where robustness is required. |
@thestinger Would an in-between API that returns a nullable ptr that this API wraps, checks, and panics for be acceptable? |
Basically I agree that always panicing/aborting isn't appropriate for all usecases, but it's a common enough strategy that having some API that does it for you seems reasonable to me. |
I don't think it's a common use case. There are not meant to be many users of this API, and the fact that more and more code is calling it directly is bad design. There isn't a good excuse for reinventing the same wheels over and over via low-level |
There should only be one user of this API, like a version of |
(and with only one user of the API, it doesn't make sense to have it) |
Even the existence of a |
The same thing applies to every other user of |
The only reason I can think of is the ability to use |
To enforce non-zero length values, you could use @luqmana's |
Closing this for now. @pnkfelix would rather a more robust longterm solution. I have moved this to my raw-rs project. |
Observe unsafeness when generating manual impls of former derives
This adds a alloc::faux module that implements an interpretation of @pnkfelix's proposed high-level allocator API (rust-lang/rfcs#244), optimized for the libstd usecases. This moves all of the tedious oom/zst/capacity-overflow logic into a central place, so that collections and other things don't need to think about it.
As part of this PR I have migrated RingBuf to this API as a proof of concept. Migrating Vec will likely be more tricky, because it does a lot more special tricks based on ZSTs, making the logic more tangled. It is possible that migrating to this API would cause a perf regression for duplicated checks/extra branching. However most of the checks are largely based on compile-time-constants, so I am optimistic about LLVM's ability to optimize these to death.
I guessed out the best semantics I could for some iffy things, like how the
alloc_array
API should handlelen = 0
.I also am considering adding triple_alloc_array/triple_dealloc_array methods to handle the HashMap usecase (which BTree can also benefit from longterm).