From e69bd722905d97a709a9bcfe988b24e5538d1a95 Mon Sep 17 00:00:00 2001 From: Francis De Brabandere Date: Sun, 1 Feb 2026 18:03:05 +0100 Subject: [PATCH] perf: optimize MiniFAT free sector management Adds a worst case benchmark for MiniFAT write --- benches/benchmark.rs | 26 ++++++++++++++++++++++++++ src/internal/alloc.rs | 3 +++ src/internal/minialloc.rs | 31 ++++++++++++++++++++++++------- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 0fb4740..ca111d9 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -107,6 +107,32 @@ fn criterion_benchmark(c: &mut Criterion) { } small.finish(); + // streams just below the MiniFAT cutoff + let mut cutoff = c.benchmark_group("write MiniFAT max streams"); + let size = (4 * 1024) - 1; + let n = 500; + let total_bytes = (n * size) as u64; + cutoff.sample_size(10); + cutoff.throughput(Throughput::Bytes(total_bytes)); + + for (buf, label) in buffer_sizes { + cutoff.bench_with_input( + BenchmarkId::new("total", *label), + &size, + |b, &s| { + b.iter(|| { + let out = write_many_streams( + black_box(n), + black_box(s), + buf.map(black_box), + ); + black_box(out); + }) + }, + ); + } + cutoff.finish(); + // several medium streams with throughput reporting let mut medium = c.benchmark_group("write several medium streams"); let size = 1024 * 1024usize; diff --git a/src/internal/alloc.rs b/src/internal/alloc.rs index 1621405..1fef5c0 100644 --- a/src/internal/alloc.rs +++ b/src/internal/alloc.rs @@ -342,6 +342,9 @@ impl Allocator { /// Deallocates the specified sector. fn free_sector(&mut self, sector_id: u32) -> io::Result<()> { + if self.fat.get(sector_id as usize) == Some(&consts::FREE_SECTOR) { + invalid_input!("sector {} freed twice", sector_id); + } self.set_fat(sector_id, consts::FREE_SECTOR)?; self.free_sectors.push(sector_id); // TODO: Truncate FAT if last FAT sector is now all free. diff --git a/src/internal/minialloc.rs b/src/internal/minialloc.rs index e0a8bc9..2828bba 100644 --- a/src/internal/minialloc.rs +++ b/src/internal/minialloc.rs @@ -26,6 +26,7 @@ pub struct MiniAllocator { directory: Directory, minifat: Vec, minifat_start_sector: u32, + free_mini_sectors: Vec, } impl MiniAllocator { @@ -35,8 +36,12 @@ impl MiniAllocator { minifat_start_sector: u32, validation: Validation, ) -> io::Result> { - let mut minialloc = - MiniAllocator { directory, minifat, minifat_start_sector }; + let mut minialloc = MiniAllocator { + directory, + minifat, + minifat_start_sector, + free_mini_sectors: Vec::new(), + }; minialloc.validate(validation)?; Ok(minialloc) } @@ -139,6 +144,13 @@ impl MiniAllocator { pointees.insert(to_mini_sector); } } + + self.free_mini_sectors.clear(); + for (idx, &entry) in self.minifat.iter().enumerate() { + if entry == consts::FREE_SECTOR { + self.free_mini_sectors.push(idx as u32); + } + } Ok(()) } } @@ -237,11 +249,10 @@ impl MiniAllocator { /// returns the new mini sector number. fn allocate_mini_sector(&mut self, value: u32) -> io::Result { // If there's an existing free mini sector, use that. - for mini_sector in 0..self.minifat.len() { - if self.minifat[mini_sector] == consts::FREE_SECTOR { - let mini_sector = mini_sector as u32; - self.set_minifat(mini_sector, value)?; - return Ok(mini_sector); + while let Some(free_idx) = self.free_mini_sectors.pop() { + if self.minifat[free_idx as usize] == consts::FREE_SECTOR { + self.set_minifat(free_idx, value)?; + return Ok(free_idx); } } // Otherwise, we need a new mini sector; if there's not room in the @@ -305,7 +316,11 @@ impl MiniAllocator { /// Deallocates the specified mini sector. fn free_mini_sector(&mut self, mini_sector: u32) -> io::Result<()> { + if self.minifat[mini_sector as usize] == consts::FREE_SECTOR { + invalid_input!("sector {} freed twice", mini_sector); + } self.set_minifat(mini_sector, consts::FREE_SECTOR)?; + self.free_mini_sectors.push(mini_sector); let mut mini_stream_len = self.directory.root_dir_entry().stream_len; debug_assert_eq!(mini_stream_len % consts::MINI_SECTOR_LEN as u64, 0); while self.minifat.last() == Some(&consts::FREE_SECTOR) { @@ -313,6 +328,8 @@ impl MiniAllocator { self.minifat.pop(); // TODO: Truncate MiniFAT if last MiniFAT sector is now all free. } + let minifat_len = self.minifat.len(); + self.free_mini_sectors.retain(|&idx| (idx as usize) < minifat_len); if mini_stream_len != self.directory.root_dir_entry().stream_len { self.directory.with_root_dir_entry_mut(|dir_entry| {