diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 23113e5..28500d6 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -10,22 +10,29 @@ use std::io::Write; use std::time::Duration; use tempfile::NamedTempFile; +// The buffers a case writes into or reads into are created once per case and +// reused across iterations, so that the measurements cover the crate rather +// than the kernel mapping fresh memory: for the large sizes, filling a newly +// allocated Vec costs several times the copy itself. + +/// Writes `n` streams of `data` into `buff`, reusing its allocation. +fn write_many_streams_into(buff: &mut Vec, data: &[u8], n: usize) { + buff.clear(); + let mut test_comp = CompoundFile::create(Cursor::new(buff)).unwrap(); + for i in 0..n { + let name = format!("test{i}"); + let mut stream = test_comp.create_stream(name).unwrap(); + stream.write_all(data).unwrap(); + } +} + fn write_many_streams(n: usize, size: usize) -> Vec { let mut buff = Vec::new(); - { - let mut test_comp = - CompoundFile::create(Cursor::new(&mut buff)).unwrap(); - let data = vec![0; size]; - for i in 0..n { - let name = format!("test{i}"); - let mut stream = test_comp.create_stream(name).unwrap(); - stream.write_all(&data).unwrap(); - } - } + write_many_streams_into(&mut buff, &vec![0; size], n); buff } -fn write_many_streams_to_file(tmpfile: &NamedTempFile, n: usize, size: usize) { +fn write_many_streams_to_file(tmpfile: &NamedTempFile, data: &[u8], n: usize) { let mut test_comp = CompoundFile::create( OpenOptions::new() .read(true) @@ -34,41 +41,45 @@ fn write_many_streams_to_file(tmpfile: &NamedTempFile, n: usize, size: usize) { .unwrap(), ) .unwrap(); - let data = vec![0; size]; for i in 0..n { let name = format!("test{i}"); let mut stream = test_comp.create_stream(name).unwrap(); - stream.write_all(&data).unwrap(); + stream.write_all(data).unwrap(); } } -fn write_many_streams_disk(n: usize, size: usize) { +fn write_many_streams_disk(data: &[u8], n: usize) { let tmpfile = NamedTempFile::new().unwrap(); - write_many_streams_to_file(&tmpfile, n, size); + write_many_streams_to_file(&tmpfile, data, n); // File is deleted when tmpfile is dropped } -fn read_many_streams_disk(tmpfile: &NamedTempFile, n: usize) { - // An unbuffered File, as `cfb::open` hands out. - let mut test_comp = - CompoundFile::open(File::open(tmpfile.path()).unwrap()).unwrap(); +/// Reads every stream into `sink`, reusing its allocation. +fn read_many_streams(buff: &[u8], n: usize, sink: &mut Vec) { + let mut test_comp = CompoundFile::open(Cursor::new(buff)).unwrap(); for i in 0..n { let name = format!("test{i}"); let mut stream = test_comp.open_stream(name).unwrap(); - let mut sink = Vec::new(); - stream.read_to_end(&mut sink).unwrap(); - black_box(sink); + sink.clear(); + stream.read_to_end(sink).unwrap(); + black_box(&sink); } } -fn read_many_streams(buff: &[u8], n: usize) { - let mut test_comp = CompoundFile::open(Cursor::new(buff)).unwrap(); +fn read_many_streams_disk( + tmpfile: &NamedTempFile, + n: usize, + sink: &mut Vec, +) { + // An unbuffered File, as `cfb::open` hands out. + let mut test_comp = + CompoundFile::open(File::open(tmpfile.path()).unwrap()).unwrap(); for i in 0..n { let name = format!("test{i}"); let mut stream = test_comp.open_stream(name).unwrap(); - let mut sink = Vec::new(); - stream.read_to_end(&mut sink).unwrap(); - black_box(sink); + sink.clear(); + stream.read_to_end(sink).unwrap(); + black_box(&sink); } } @@ -79,7 +90,7 @@ fn criterion_benchmark(c: &mut Criterion) { ("n=1000,size=64B", 64usize, 1000usize), ("n=100,size=4KiB-1 (MiniFAT)", 1024 * 4 - 1, 100usize), ("n=100,size=4KiB (FAT)", 1024 * 4, 100usize), - ("n=50,size=1MiB", 1024 * 1024usize, 50usize), + ("n=256,size=1MiB", 1024 * 1024usize, 256usize), ("n=1,size=256MiB", 256 * 1024 * 1024usize, 1usize), ]; @@ -90,13 +101,15 @@ fn criterion_benchmark(c: &mut Criterion) { if total_bytes > 0 { group.throughput(Throughput::Bytes(total_bytes)); } + let data = vec![0u8; stream_size]; + let mut buff = Vec::new(); group.bench_function(label, |b| { b.iter(|| { - let out = write_many_streams( + write_many_streams_into( + black_box(&mut buff), + black_box(&data), black_box(stream_count), - black_box(stream_size), ); - black_box(out); }) }); } @@ -109,11 +122,12 @@ fn criterion_benchmark(c: &mut Criterion) { if total_bytes > 0 { disk_group.throughput(Throughput::Bytes(total_bytes)); } + let data = vec![0u8; stream_size]; disk_group.bench_function(label, |b| { b.iter(|| { write_many_streams_disk( + black_box(&data), black_box(stream_count), - black_box(stream_size), ); }) }); @@ -128,9 +142,14 @@ fn criterion_benchmark(c: &mut Criterion) { if total_bytes > 0 { read_group.throughput(Throughput::Bytes(total_bytes)); } + let mut sink = Vec::new(); read_group.bench_function(label, |b| { b.iter(|| { - read_many_streams(black_box(&buff), black_box(stream_count)); + read_many_streams( + black_box(&buff), + black_box(stream_count), + &mut sink, + ); }) }); } @@ -140,7 +159,12 @@ fn criterion_benchmark(c: &mut Criterion) { for (label, stream_size, stream_count) in stream_benches { let total_bytes = (stream_count * stream_size) as u64; let tmpfile = NamedTempFile::new().unwrap(); - write_many_streams_to_file(&tmpfile, stream_count, stream_size); + write_many_streams_to_file( + &tmpfile, + &vec![0; stream_size], + stream_count, + ); + let mut sink = Vec::new(); // Disk cases are slow per iteration; keep the group's run time down. read_disk_group.sample_size(10); read_disk_group.warm_up_time(Duration::from_secs(1)); @@ -153,6 +177,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_many_streams_disk( black_box(&tmpfile), black_box(stream_count), + &mut sink, ); }) });