From 5e6bbed5fb548fee9fb1a4767810bbc4ed009720 Mon Sep 17 00:00:00 2001 From: Artem Rakov Date: Tue, 9 Apr 2024 21:54:01 -0400 Subject: [PATCH] perf: avoid initializing huge buffers --- Cargo.toml | 5 +++ benches/buffers.rs | 86 ++++++++++++++++++++++++++++++++++++++++ src/stream/buf_reader.rs | 11 +++-- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 benches/buffers.rs diff --git a/Cargo.toml b/Cargo.toml index c301500d..248ceb6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,11 @@ name = "mp4" harness = false required-features = ["mp4"] +[[bench]] +name = "buffers" +harness = false +required-features = ["std"] + [[example]] name = "async" required-features = ["std", "tokio"] diff --git a/benches/buffers.rs b/benches/buffers.rs new file mode 100644 index 00000000..6716cf1d --- /dev/null +++ b/benches/buffers.rs @@ -0,0 +1,86 @@ +#![cfg(feature = "std")] + +use { + combine::{ + parser::{ + byte::take_until_bytes, + combinator::{any_send_sync_partial_state, recognize, AnySendSyncPartialState}, + }, + Parser, RangeStream, + }, + criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion}, + partial_io::{PartialOp, PartialRead}, + std::io::Cursor, +}; + +fn test_data() -> Vec { + let mut input = vec![b' '; 5_000_000]; + input.push(b'1'); + + input +} + +fn parser<'a, I>() -> impl combine::Parser +where + I: RangeStream, + I::Error: combine::ParseError, +{ + any_send_sync_partial_state( + recognize(take_until_bytes(&b"1"[..])).map(|spaces: Vec| spaces.len()), + ) +} + +fn bench_small_buf(bencher: &mut Bencher<'_>) { + let input = test_data(); + let mut decoder = combine::stream::decoder::Decoder::new(); + + bencher.iter(|| { + let cursor = Cursor::new(&input); + let mut partial_read = + PartialRead::new(cursor, std::iter::repeat(PartialOp::Limited(1000))); + let mut ref_decoder = &mut decoder; + + let result = combine::decode!(ref_decoder, partial_read, parser(), |input, _position| { + combine::easy::Stream::from(input) + },); + + match result { + Ok(usize) => black_box(usize), + Err(err) => { + println!("{:?}", err); + panic!(); + } + }; + }); +} + +fn bench_big_buf(bencher: &mut Bencher<'_>) { + let input = test_data(); + let mut decoder = combine::stream::decoder::Decoder::new(); + + bencher.iter(|| { + let cursor = Cursor::new(&input); + let mut partial_read = PartialRead::new(cursor, std::iter::repeat(PartialOp::Unlimited)); + let mut ref_decoder = &mut decoder; + + let result = combine::decode!(ref_decoder, partial_read, parser(), |input, _position| { + combine::easy::Stream::from(input) + },); + + match result { + Ok(usize) => black_box(usize), + Err(err) => { + println!("{:?}", err); + panic!(); + } + }; + }); +} + +fn bench(c: &mut Criterion) { + c.bench_function("buffers_small", bench_small_buf); + c.bench_function("buffers_big", bench_big_buf); +} + +criterion_group!(buffers, bench); +criterion_main!(buffers); diff --git a/src/stream/buf_reader.rs b/src/stream/buf_reader.rs index fa7b928b..e6c27629 100644 --- a/src/stream/buf_reader.rs +++ b/src/stream/buf_reader.rs @@ -8,11 +8,7 @@ use std::io::{self, BufRead, Read}; ))] use std::pin::Pin; -#[cfg(any( - feature = "futures-03", - feature = "tokio-02", - feature = "tokio-03" -))] +#[cfg(any(feature = "futures-03", feature = "tokio-02", feature = "tokio-03"))] use std::mem::MaybeUninit; #[cfg(feature = "futures-core-03")] @@ -361,14 +357,17 @@ fn extend_buf_sync(buf: &mut BytesMut, read: &mut R) -> io::Result where R: Read, { + let size = 8 * 1024; if !buf.has_remaining_mut() { - buf.reserve(8 * 1024); + buf.reserve(size); } // Copy of tokio's poll_read_buf method (but it has to force initialize the buffer) let n = { let bs = buf.chunk_mut(); + let initial_size = bs.len().min(size); + let bs = &mut bs[..initial_size]; for i in 0..bs.len() { bs.write_byte(i, 0); }