-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Summary
Add an Allocator::deallocate_zeroed
trait method that is a dual to allocate_zeroed
. It would inherit Allocator::deallocate
's unsafe
contract and additionally require that callers only pass memory that is already zeroed.
trait Allocator {
// ...
unsafe fn deallocate_zeroed(&self, ptr: NonNull<u8>, layout: Layout) { ... }
}
Motivation
I am working in a soft real-time embedded system where
- I need to allocate large blocks of zeroed memory on the critical path
- I cannot use virtual memory or make syscalls
Because of (2) I cannot rely on fresh mmap
s to get zero pages, use madvise(DONTNEED)
, or let the OS zero pages in the background for me.
Instead, I use a custom allocator which keeps track of already-zeroed memory blocks. This makes on-demand zeroing during allocation unnecessary, and we can avoid zeroing memory on the critical path. The zeroing is performed asynchronously, off the critical path, in a cooperatively-yielding loop that zeroes up-to-N-bytes-large chunks of a memory block during each iteration. Additionally, I can sometimes avoid zeroing the full memory block, relying on application logic to determine that certain portions of the (initially zero) memory block were not modified (and therefore remain zeroed). Once the memory block has been fully zeroed, it is then returned to the allocator, which inserts the block into its already-zeroed freelist.1
Anyways, this system works if I call my allocator's methods directly, but it does not play nice with the Allocator
trait. This prevents abstraction, librarification, and separation of concerns.
Open Questions
-
Should this be a provided method? If so, what should the default be?
-
Should it be effectively a
memset(0)
followed byself.free
?core::ptr::write_bytes(pointer, 0, size) self.free(pointer)
That would always be correct, and is ideal when there is no OS/virtual memory. However, it may be undesirable for large allocations when virtual memory and syscalls are available, and you can just do the equivalent of
madvise(DONTNEED)
. The tricky bit is that (AFAIK) there isn't a mechanism forstd
to override default provided methods of traits fromalloc
. I suppose concreteAllocator
implementations can always override the method to domadvise(DONTNEED)
themselves, but if every implementation is doing the same override under the same conditions, then that seems unfortunate. -
Alternatively, the method could be fallible, and could simply return an error by default and force callers to figure out what to do in that case. This essentially un-asks the previous question around
memset(0)
vsmadvise(DONTNEED)
. But it doesn't really seem like all callers can make effective decisions here, what should a collection library do if the method returns an error? Unclear. -
We could also make it required method, but that seems like an annoying complication to an otherwise-simple trait. Especially for a relatively niche use case.
-
Alternatives
- The biggest alternative I can think of is to simply not add this trait method, force people with these use cases to tie their application to a specific allocator, and accept that librarification is unattainable. I don't think this is the end of the world, but I do think it is not ideal.
Footnotes
-
This system is similar to the concurrent bulk zeroing described in Why Nothing Matters: The Impact of Zeroing by Yang et al, and
Allocator::allocate_zeroed
's default implementation is what that paper calls "hot-path zeroing". Essentially, theAllocator
trait only supports lazy, on-demand, "hot-path" zeroing. Adding this method would extend its support to eager, concurrent, background zeroing. ↩