Skip to content

Commit 4468b00

Browse files
committed
Perf: Add BufferedIoRead for ~28.6% faster from_reader parsing
1 parent ce410dd commit 4468b00

File tree

5 files changed

+739
-3
lines changed

5 files changed

+739
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,4 @@ raw_value = []
9292
# to be careful around other recursive operations on the parsed result which may
9393
# overflow the stack after deserialization has completed, including, but not
9494
# limited to, Display and Debug and Drop impls.
95-
unbounded_depth = []
95+
unbounded_depth = []

src/de.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use serde::forward_to_deserialize_any;
1919
#[cfg(feature = "arbitrary_precision")]
2020
use crate::number::NumberDeserializer;
2121

22-
pub use crate::read::{Read, SliceRead, StrRead};
22+
pub use crate::read::{BufferedIoRead, Read, SliceRead, StrRead};
2323

2424
#[cfg(feature = "std")]
2525
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
@@ -2700,3 +2700,82 @@ where
27002700
{
27012701
from_trait(read::StrRead::new(s))
27022702
}
2703+
2704+
/// Deserializes an instance of type `T` from an I/O stream using an
2705+
/// internal buffer for high performance.
2706+
///
2707+
/// This function is the high-performance counterpart to [`from_reader`].
2708+
///
2709+
/// It wraps the given `io::Read` source in a [`read::BufferedIoRead`]
2710+
/// struct. This specialized reader avoids the per-byte processing overhead
2711+
/// of a simple `io::Read` wrapper (like that used by [`from_reader`])
2712+
/// by reading data in chunks into an internal buffer. It then applies the
2713+
/// same highly-optimized, `memchr`-based parsing logic used for slices
2714+
/// (`SliceRead`) to this internal buffer.
2715+
///
2716+
/// This method is significantly faster (e.g., **~28.6% faster** on a 2.2MB
2717+
/// JSON file) than using `from_reader` even with an external
2718+
/// `std::io::BufReader`, as it entirely eliminates the per-byte
2719+
/// bookkeeping cost during parsing.
2720+
///
2721+
/// ---
2722+
///
2723+
/// ### Buffer Size
2724+
///
2725+
/// This function creates a [`read::BufferedIoRead`] with a **default
2726+
/// internal buffer** (currently 128 bytes).
2727+
///
2728+
/// For most use cases, this default is a good starting point. If you are
2729+
/// parsing from a very slow I/O source or need to control the buffer
2730+
/// size (e.g., to use a larger 8KB buffer) or its allocation, you can
2731+
/// construct a [`read::BufferedIoRead`] manually with your own buffer
2732+
/// and pass it to the generic [`from_trait`] function.
2733+
///
2734+
/// ### Features
2735+
///
2736+
/// This function is only available when the `std` feature is enabled.
2737+
///
2738+
/// # Example
2739+
///
2740+
/// ```
2741+
/// use serde::Deserialize;
2742+
///
2743+
/// use std::error::Error;
2744+
/// use std::fs::File;
2745+
/// use std::io::BufReader;
2746+
/// use std::path::Path;
2747+
///
2748+
/// #[derive(Deserialize, Debug)]
2749+
/// struct User {
2750+
/// fingerprint: String,
2751+
/// location: String,
2752+
/// }
2753+
///
2754+
/// fn read_user_from_file<P: AsRef<Path>>(path: P) -> Result<User, Box<dyn Error>> {
2755+
/// // Open the file in read-only mode.
2756+
/// let file = File::open(path)?;
2757+
///
2758+
/// // Read the JSON contents of the file as an instance of `User` with default buffer.
2759+
/// let u = serde_json::from_reader_buffered(file)?;
2760+
///
2761+
/// // Return the `User`.
2762+
/// Ok(u)
2763+
/// }
2764+
///
2765+
/// fn main() {
2766+
/// # }
2767+
/// # fn fake_main() {
2768+
/// let u = read_user_from_file("test.json").unwrap();
2769+
/// println!("{:#?}", u);
2770+
/// }
2771+
/// ```
2772+
#[cfg(feature = "std")]
2773+
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
2774+
pub fn from_reader_buffered<R, T>(rdr: R) -> Result<T>
2775+
where
2776+
R: crate::io::Read,
2777+
T: de::DeserializeOwned,
2778+
{
2779+
let b_rdr: read::BufferedIoRead<R> = read::BufferedIoRead::new(rdr, [0; _]);
2780+
from_trait(b_rdr)
2781+
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ pub mod __private {
391391
#[cfg(feature = "std")]
392392
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
393393
#[doc(inline)]
394-
pub use crate::de::from_reader;
394+
pub use crate::de::{from_reader, from_reader_buffered};
395395
#[doc(inline)]
396396
pub use crate::de::{from_slice, from_str, Deserializer, StreamDeserializer};
397397
#[doc(inline)]

0 commit comments

Comments
 (0)