Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions oscars/src/alloc/mempool3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,21 @@ impl From<LayoutError> for PoolAllocError {
}
}

const SIZE_CLASSES: &[usize] = &[16, 24, 32, 48, 64, 96, 128, 192, 256, 512, 1024, 2048];
const SIZE_CLASSES: &[usize] = &[
16, 24, 32, 48, 64, 96, 128, 192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
];

#[inline(always)]
fn size_class_index_for(size: usize) -> usize {
// binary search over size classes
let idx = SIZE_CLASSES.partition_point(|&sc| sc < size);
debug_assert!(
assert!(
idx < SIZE_CLASSES.len(),
"object size {size}B exceeds the largest size class ({}B); \
consider adding a larger class",
SIZE_CLASSES.last().unwrap()
);
idx.min(SIZE_CLASSES.len() - 1)
idx
}

const DEFAULT_PAGE_SIZE: usize = 262_144;
Expand All @@ -58,7 +60,7 @@ pub struct PoolAllocator<'alloc> {
// cached index of the last pool used by free_slot
pub(crate) free_cache: Cell<usize>,
// per size class cached index of the last pool used by alloc_slot
pub(crate) alloc_cache: [Cell<usize>; 12],
pub(crate) alloc_cache: [Cell<usize>; 17],
// empty slot pools kept alive to avoid OS reallocation on the next cycle
pub(crate) recycled_pools: Vec<SlotPool>,
// maximum number of idle pages held across all size classes
Expand All @@ -78,20 +80,7 @@ impl<'alloc> Default for PoolAllocator<'alloc> {
slot_pools: Vec::new(),
bump_pages: Vec::new(),
free_cache: Cell::new(usize::MAX),
alloc_cache: [
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
Cell::new(usize::MAX),
],
alloc_cache: core::array::from_fn(|_| Cell::new(usize::MAX)),
recycled_pools: Vec::new(),
// keep two empty pages per size class to reduce OS overhead
max_recycled: SIZE_CLASSES.len() * 2,
Expand Down
30 changes: 30 additions & 0 deletions oscars/src/alloc/mempool3/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,33 @@ fn max_recycled_cap_respected() {
assert_eq!(allocator.recycled_pools.len(), 1);
assert!(allocator.current_heap_size < heap_before);
}

#[test]
fn alloc_large_object_success() {
let mut allocator = PoolAllocator::default().with_page_size(16384);
// allocate a size that maps to a class > 2048
// e.g. 4096 size class => size 3000
struct LargeObject {
_data: [u8; 3000],
}
let a = allocator
.try_alloc(LargeObject { _data: [0; 3000] })
.unwrap();
assert_eq!(allocator.pools_len(), 1);

// Test that the caching works for the new size classes
let _b = allocator
.try_alloc(LargeObject { _data: [0; 3000] })
.unwrap();
assert_eq!(allocator.pools_len(), 1); // should still fit in the same page
}

#[test]
#[should_panic(expected = "object size")]
fn alloc_oversized_object_panics() {
let mut allocator = PoolAllocator::default().with_page_size(128 * 1024);
struct OversizedObject {
_data: [u8; 70000],
}
let _ = allocator.try_alloc(OversizedObject { _data: [0; 70000] });
}