/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use std::{
alloc::Layout,
ptr::{self, NonNull}, sync::{Arc, Mutex},
};
use allocator_api2::alloc::{AllocError, Allocator, Global};
/// A simple bump allocator, sub-allocating from fixed size chunks that are provided /// by a parent allocator. /// /// If an allocation is larger than the chunk size, a chunk sufficiently large to contain /// the allocation is added. pubstruct BumpAllocator { /// The chunk we are currently allocating from.
current_chunk: NonNull<Chunk>, /// For debugging.
allocation_count: i32,
/// A Contiguous buffer of memory holding multiple sub-allocaions. pubstruct Chunk {
previous: Option<NonNull<Chunk>>, /// Offset of the next allocation
cursor: *mut u8, /// Points to the first byte after the chunk's buffer.
chunk_end: *mut u8, /// Size of the chunk
size: usize,
}
impl Chunk { pubfn allocate_item(this: NonNull<Chunk>, layout: Layout) -> Result<NonNull<[u8]>, ()> { // Common wisdom would be to always bump address downward (https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html). // However, bump allocation does not show up in profiles with the current workloads // so we can keep things simple for now.
debug_assert!(CHUNK_ALIGNMENT % layout.align() == 0);
debug_assert!(layout.align() > 0);
debug_assert!(layout.align().is_power_of_two());
let size = align(layout.size(), CHUNK_ALIGNMENT);
unsafe { let cursor = (*this.as_ptr()).cursor; let end = (*this.as_ptr()).chunk_end; let available_size = end.offset_from(cursor);
if size as isize > available_size { return Err(());
}
let next = cursor.add(size);
(*this.as_ptr()).cursor = next;
let cursor = NonNull::new(cursor).unwrap(); let suballocation: NonNull<[u8]> = NonNull::slice_from_raw_parts(cursor, size);
unsafe { let size = align(layout.size(), CHUNK_ALIGNMENT); let item_end = item.as_ptr().add(size);
// If the item is the last allocation, then move the cursor back // to reuse its memory. if item_end == (*this.as_ptr()).cursor {
(*this.as_ptr()).cursor = item.as_ptr();
}
let old_size = align(old_layout.size(), CHUNK_ALIGNMENT); let new_size = align(new_layout.size(), CHUNK_ALIGNMENT); let old_item_end = item.as_ptr().add(old_size);
if old_item_end != (*this.as_ptr()).cursor { return Err(());
}
// The item is the last allocation. we can attempt to just move // the cursor if the new size fits.
let chunk_end = (*this.as_ptr()).chunk_end; let available_size = chunk_end.offset_from(item.as_ptr());
if new_size as isize > available_size { // Does not fit. return Err(());
}
let new_item_end = item.as_ptr().add(new_size);
(*this.as_ptr()).cursor = new_item_end;
let old_size = align(old_layout.size(), CHUNK_ALIGNMENT); let new_size = align(new_layout.size(), CHUNK_ALIGNMENT); let old_item_end = item.as_ptr().add(old_size);
// The item is the last allocation. we can attempt to just move // the cursor if the new size fits.
if old_item_end == (*this.as_ptr()).cursor { let new_item_end = item.as_ptr().add(new_size);
(*this.as_ptr()).cursor = new_item_end;
}
NonNull::slice_from_raw_parts(item, new_size)
}
pubfn contains_item(this: NonNull<Chunk>, item: NonNull<u8>) -> bool { unsafe { let start: *mut u8 = this.cast::<u8>().as_ptr().add(CHUNK_ALIGNMENT); let end: *mut u8 = (*this.as_ptr()).chunk_end; let item = item.as_ptr();
start <= item && item < end
}
}
fn available_size(this: NonNull<Chunk>) -> usize { unsafe { let this = this.as_ptr();
(*this).chunk_end.offset_from((*this).cursor) as usize
}
}
fn utilization(this: NonNull<Chunk>) -> f32 { let size = unsafe { (*this.as_ptr()).size } as f32;
(size - Chunk::available_size(this) as f32) / size
}
}
fn align(val: usize, alignment: usize) -> usize { let rem = val % alignment; if rem == 0 { return val;
}
/// A simple pool for allocating and recycling memory chunks of a fixed size, /// protected by a mutex. /// /// Chunks in the pool are stored as a linked list using a pointer to the next /// element at the beginning of the chunk. pubstruct ChunkPool {
inner: Mutex<ChunkPoolInner>,
}
/// Pop a chunk from the pool or allocate a new one. /// /// If the requested size is not equal to the default chunk size, /// a new chunk is allocated. pubfn allocate_chunk(&self, size: usize) -> Result<NonNull<Chunk>, AllocError> { let chunk: Option<NonNull<RecycledChunk>> = if size == DEFAULT_CHUNK_SIZE { // Try to reuse a chunk. letmut inner = self.inner.lock().unwrap(); letmut chunk = inner.first.take();
inner.first = chunk.as_mut().and_then(|chunk| unsafe { chunk.as_mut().next.take() });
if chunk.is_some() {
inner.count -= 1;
debug_assert!(inner.count >= 0);
}
chunk
} else { // Always allocate a new chunk if it is not the standard size.
None
};
let chunk: NonNull<Chunk> = match chunk {
Some(chunk) => chunk.cast(),
None => { // Allocate a new one. let layout = match Layout::from_size_align(size, CHUNK_ALIGNMENT) {
Ok(layout) => layout,
Err(_) => { return Err(AllocError);
}
};
/// Put the provided list of chunks into the pool. /// /// Chunks with size different from the default chunk size are deallocated /// immediately. /// /// # Safety /// /// Ownership of the provided chunks is transfered to the pool, nothing /// else can access them after this function runs. unsafefn recycle_chunks(&self, chunk: NonNull<Chunk>) { letmut inner = self.inner.lock().unwrap(); letmut iter = Some(chunk); // Go through the provided linked list of chunks, and insert each // of them at the beginning of our linked list of recycled chunks. whilelet Some(mut chunk) = iter { // Advance the iterator.
iter = unsafe { chunk.as_mut().previous.take() };
unsafe { // Don't recycle chunks with a non-standard size. let size = chunk.as_ref().size; if size != DEFAULT_CHUNK_SIZE { let layout = Layout::from_size_align(size, CHUNK_ALIGNMENT).unwrap();
Global.deallocate(chunk.cast(), layout); continue;
}
}
// Turn the chunk into a recycled chunk. let recycled: NonNull<RecycledChunk> = chunk.cast();
// Insert into the recycled list. unsafe {
ptr::write(recycled.as_ptr(), RecycledChunk {
next: inner.first,
});
}
inner.first = Some(recycled);
inner.count += 1;
}
}
/// Deallocate chunks until the pool contains at most `target` items, or /// `count` chunks have been deallocated. /// /// Returns `true` if the target number of chunks in the pool was reached, /// `false` if this method stopped before reaching the target. /// /// Purging chunks can be expensive so it is preferable to perform this /// operation outside of the critical path. Specifying a lower `count` /// allows the caller to split the work and spread it over time. #[inline(never)] pubfn purge_chunks(&self, target: u32, mut count: u32) -> bool { letmut inner = self.inner.lock().unwrap();
assert!(inner.count >= 0);
while inner.count as u32 > target { unsafe { // First can't be None because inner.count > 0. let chunk = inner.first.unwrap();
// Pop chunk off the list.
inner.first = chunk.as_ref().next;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.