Skip to content

Commit cef82cb

Browse files
committed
chore: development v0.2.66 - comprehensive testing complete [auto-commit]
1 parent eff2fce commit cef82cb

File tree

12 files changed

+136
-23
lines changed

12 files changed

+136
-23
lines changed

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ exclude = [
3838
# Workspace Package Metadata (inherited by all crates)
3939
# ─────────────────────────────────────────────────────────────────────────────
4040
[workspace.package]
41-
version = "0.2.65"
41+
version = "0.2.66"
4242
edition = "2024"
4343
rust-version = "1.85"
4444
license = "MPL-2.0 OR LicenseRef-UFFS-Commercial"

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Traditional file search tools (including `os.walk`, `FindFirstFile`, etc.) work
2121

2222
**UFFS reads the MFT directly** - once - and queries it in memory using Polars DataFrames. This is like reading the entire phonebook once instead of looking up each name individually.
2323

24-
### Benchmark Results (v0.2.65)
24+
### Benchmark Results (v0.2.66)
2525

2626
| Drive Type | Records | Time | Throughput |
2727
|------------|---------|------|------------|
@@ -33,7 +33,7 @@ Traditional file search tools (including `os.walk`, `FindFirstFile`, etc.) work
3333

3434
| Comparison | Records | Time | Notes |
3535
|------------|---------|------|-------|
36-
| **UFFS v0.2.65** | **18.7 Million** | **~142 seconds** | All disks, fast mode |
36+
| **UFFS v0.2.66** | **18.7 Million** | **~142 seconds** | All disks, fast mode |
3737
| UFFS v0.1.30 | 18.7 Million | ~315 seconds | Baseline |
3838
| Everything | 19 Million | 178 seconds | All disks |
3939
| WizFile | 6.5 Million | 299 seconds | Single HDD |

crates/uffs-mft/src/io.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4007,11 +4007,21 @@ impl ParallelMftReader {
40074007
///
40084008
/// This eliminates the separate parse and index build phases, saving ~7s
40094009
/// on large MFTs by overlapping CPU work with I/O.
4010+
///
4011+
/// # Arguments
4012+
///
4013+
/// * `overlapped_handle` - IOCP handle for async I/O
4014+
/// * `volume` - Volume letter (e.g., 'C')
4015+
/// * `concurrency` - Number of I/O ops in flight (None = 2 for HDD)
4016+
/// * `io_chunk_size` - Size of each I/O in bytes (None = 1MB)
4017+
/// * `_progress_callback` - Optional progress callback
40104018
#[allow(unsafe_code)]
40114019
pub fn read_all_sliding_window_iocp_to_index<F>(
40124020
&self,
40134021
overlapped_handle: HANDLE,
40144022
volume: char,
4023+
concurrency: Option<usize>,
4024+
io_chunk_size: Option<usize>,
40154025
_progress_callback: Option<F>,
40164026
) -> Result<crate::index::MftIndex>
40174027
where
@@ -4031,13 +4041,14 @@ impl ParallelMftReader {
40314041
let record_size = self.extent_map.bytes_per_record as usize;
40324042
let total_records = self.extent_map.total_records() as usize;
40334043

4034-
const CONCURRENCY: usize = 2;
4035-
const IO_CHUNK_SIZE: usize = 1024 * 1024; // 1MB per read
4044+
// Use provided values or defaults
4045+
let concurrency = concurrency.unwrap_or(2);
4046+
let io_chunk_size = io_chunk_size.unwrap_or(1024 * 1024); // 1MB default
40364047

40374048
info!(
40384049
total_records,
4039-
concurrency = CONCURRENCY,
4040-
io_size_kb = IO_CHUNK_SIZE / 1024,
4050+
concurrency,
4051+
io_size_kb = io_chunk_size / 1024,
40414052
"🚀 Starting sliding window IOCP with INLINE parsing (C++ parity)"
40424053
);
40434054

@@ -4067,7 +4078,7 @@ impl ParallelMftReader {
40674078
let mut frs_offset = 0u64;
40684079

40694080
while offset_within_chunk < chunk_bytes {
4070-
let io_size = std::cmp::min(IO_CHUNK_SIZE, chunk_bytes - offset_within_chunk);
4081+
let io_size = std::cmp::min(io_chunk_size, chunk_bytes - offset_within_chunk);
40714082
let records_in_io = io_size / record_size;
40724083
let disk_offset =
40734084
chunk.disk_offset + skip_begin_bytes as u64 + offset_within_chunk as u64;
@@ -4110,19 +4121,19 @@ impl ParallelMftReader {
41104121
op: IoOp,
41114122
}
41124123

4113-
let mut buffer_pool: Vec<AlignedBuffer> = (0..CONCURRENCY)
4114-
.map(|_| AlignedBuffer::new(IO_CHUNK_SIZE))
4124+
let mut buffer_pool: Vec<AlignedBuffer> = (0..concurrency)
4125+
.map(|_| AlignedBuffer::new(io_chunk_size))
41154126
.collect();
41164127

41174128
let mut in_flight: Vec<Option<Pin<Box<InFlightOp>>>> =
4118-
(0..CONCURRENCY).map(|_| None).collect();
4129+
(0..concurrency).map(|_| None).collect();
41194130

41204131
let mut completed_count = 0usize;
41214132
let mut bytes_read_total = 0u64;
41224133
let mut records_parsed = 0usize;
41234134

41244135
// Queue initial reads
4125-
for slot_id in 0..CONCURRENCY {
4136+
for slot_id in 0..concurrency {
41264137
if let Some(op) = io_ops.pop_front() {
41274138
let buffer = buffer_pool.pop().unwrap();
41284139
let mut in_flight_op = Box::pin(InFlightOp {

crates/uffs-mft/src/main.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,18 @@ enum Commands {
442442
/// paths lazily. Disabling saves ~15% of CPU time.
443443
#[arg(long)]
444444
no_placeholders: bool,
445+
446+
/// Number of concurrent I/O operations (reads in flight).
447+
/// Default: 2 for HDD (optimal for sequential), higher for SSD/NVMe.
448+
/// Use this to experiment with I/O parallelism.
449+
#[arg(long, default_value = "2")]
450+
concurrency: usize,
451+
452+
/// I/O chunk size in KB (e.g., 1024 = 1MB, 2048 = 2MB, 4096 = 4MB).
453+
/// Default: 1024 (1MB). Larger chunks reduce syscall overhead but
454+
/// increase latency per completion.
455+
#[arg(long, default_value = "1024")]
456+
io_size_kb: usize,
445457
},
446458
}
447459

@@ -608,7 +620,19 @@ async fn run() -> Result<()> {
608620
mode,
609621
no_bitmap,
610622
no_placeholders,
611-
} => cmd_benchmark_index_lean(drive, &mode, no_bitmap, no_placeholders).await,
623+
concurrency,
624+
io_size_kb,
625+
} => {
626+
cmd_benchmark_index_lean(
627+
drive,
628+
&mode,
629+
no_bitmap,
630+
no_placeholders,
631+
concurrency,
632+
io_size_kb,
633+
)
634+
.await
635+
}
612636
}
613637
}
614638
}
@@ -2847,6 +2871,8 @@ async fn cmd_benchmark_index_lean(
28472871
mode_str: &str,
28482872
no_bitmap: bool,
28492873
no_placeholders: bool,
2874+
concurrency: usize,
2875+
io_size_kb: usize,
28502876
) -> Result<()> {
28512877
use std::time::Instant;
28522878

@@ -2870,6 +2896,8 @@ async fn cmd_benchmark_index_lean(
28702896
"enabled"
28712897
}
28722898
);
2899+
println!("Concurrency: {} I/O ops in flight", concurrency);
2900+
println!("I/O Size: {} KB ({} MB)", io_size_kb, io_size_kb / 1024);
28732901
println!("This measures the UFFS indexing pipeline with lean MftIndex (no DataFrame overhead)");
28742902
println!();
28752903

@@ -2904,12 +2932,16 @@ async fn cmd_benchmark_index_lean(
29042932
// Open reader and read MFT into lean index
29052933
// - no_bitmap: disable bitmap optimization to read entire MFT sequentially
29062934
// - no_placeholders: skip placeholder creation for ~15% speedup
2935+
// - concurrency: number of I/O ops in flight
2936+
// - io_size_kb: I/O chunk size in KB
29072937
let reader = MftReader::open(drive_upper)
29082938
.await
29092939
.with_context(|| format!("Failed to open drive {}:", drive_upper))?
29102940
.with_mode(mode)
29112941
.with_use_bitmap(!no_bitmap)
2912-
.with_add_placeholders(!no_placeholders);
2942+
.with_add_placeholders(!no_placeholders)
2943+
.with_concurrency(concurrency)
2944+
.with_io_size(io_size_kb * 1024);
29132945

29142946
let index = reader
29152947
.read_all_index()

0 commit comments

Comments
 (0)