In my kernel I have a very small heap (1 MB). I can make this larger, but I could never make it large enough for what the function I'm trying to optimize does.
My virtual memory manager accepts memory information from my bootloader. Since this information isn't clone and copy (its a pointer), I manually duplicate the information and store it in a static:
// For now I use a stack-allocated Vec -- my memory manager is kinda broken at the moment.
static MMAP: Once<Vec<MemoryRegion, 1024>> = Once::new();
// ...
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct MemoryRegion {
pub start: u64,
pub end: u64,
pub kind: MemoryRegionKind,
}
/// Initializes the internal memory map.
#[cold]
pub fn init_memory_map(map: &'static MemoryRegions, rsdpaddr: u64) {
info!(
"Loading free memory region list from memory map at {:p}",
&map
);
MMAP.call_once(|| {
let mut mmap: Vec<MemoryRegion, 1024> = Vec::new();
map.iter().for_each(|region| {
mmap.push(MemoryRegion {
start: region.start,
end: region.end,
kind: region.kind,
})
.unwrap();
STOTAL.fetch_add(region.end - region.start, Ordering::Relaxed);
});
mmap
});
info!("Discovered {} bytes of RAM", STOTAL.load(Ordering::Relaxed));
info!("RSDP at {:X}", rsdpaddr);
RSDP.swap(rsdpaddr, Ordering::Relaxed);
}
This init_memory_map
function is only called once, hence the use of the cold
attribute. The function that's (potentially) on the really, really hot path is this portion of code:
// For context only
static FRAME_ALLOCATOR: Lazy<TicketMutex<Option<GlobalFrameAllocator>>> =
Lazy::new(|| TicketMutex::new(None));
// Position of frame that we want to get
static FPOS: AtomicUsize = AtomicUsize::new(0);
// ...
#[derive(Debug, Copy, Clone)]
struct GlobalFrameAllocator;
impl GlobalFrameAllocator {
/// Initializes the global frame allocator
#[cold]
pub fn init() -> Self {
FPOS.store(0, Ordering::Relaxed);
GlobalFrameAllocator {}
}
}
unsafe impl FrameAllocator<Size4KiB> for GlobalFrameAllocator {
#[must_use]
fn allocate_frame(&mut self) -> Option<PhysFrame> {
if MMAP.is_completed() {
FPOS.fetch_add(1, Ordering::SeqCst);
return MMAP
.get()
.unwrap()
.iter()
.map(|r| r.start..r.end)
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
.nth(FPOS.load(Ordering::Relaxed));
} else {
warn!("Memory allocation attempted when MMAP was not ready");
warn!("Waiting for memory map to be ready...");
FPOS.fetch_add(1, Ordering::SeqCst);
return MMAP
.wait()
.iter()
.map(|r| r.start..r.end)
.flat_map(|r| r.step_by(4096))
.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
.nth(FPOS.load(Ordering::Relaxed));
}
}
}
impl FrameDeallocator<Size4KiB> for GlobalFrameAllocator {
unsafe fn deallocate_frame(&mut self, _: PhysFrame) {
FPOS.fetch_sub(1, Ordering::SeqCst);
}
}
The reason this code is on the really hot path (the boiling path?) is because every time that we want a new frame of memory for a page we're allocating we call the allocate_frame
function, and every time we want to remove a new frame (or, in this case, just go back a step to "free" a frame) we call deallocate_frame
. (In future I hope to have a "proper" frame deallocator, but this suffices for now.)
As it currently stands, the frame allocation routine is quite complicated. From what I understand of iterators, these particular ones are not zero-cost. The top-level conditional branch is necessary; we don't want the memory frame allocator being used before there are frames. I could eliminate this by just calling wait()
on MMAP
, but that wouldn't get rid of the branch and would only complicate the code even more.
I love iterators, I really do. My entire codebase is riddled with them because I enjoy using them and they make programming quite pleasant. (The only place I don't use them is in PCI enumeration where I need to recursively call async functions, and async closures aren't stable yet.) How could I optimize this code and when would an iterator be a bad thing? I understand that the above allocate_frame
function is sort-of equivalent to loops and conditional statements in other languages.
I'd appreciate your thoughts and ideas.