Skip to content

Commit

Permalink
Rollup merge of rust-lang#80201 - saethlin:bufreader-read-exact, r=Ko…
Browse files Browse the repository at this point in the history
…drAus

Add benchmark and fast path for BufReader::read_exact

At work, we have a wrapper type that implements this optimization. It would be nice if the standard library were faster.

Before:
```
test io::buffered::tests::bench_buffered_reader_small_reads       ... bench:       7,670 ns/iter (+/- 45)
```
After:
```
test io::buffered::tests::bench_buffered_reader_small_reads       ... bench:       4,457 ns/iter (+/- 41)
```
  • Loading branch information
m-ou-se authored Jan 17, 2021
2 parents ea81a28 + 4e27ed3 commit 1448a76
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 17 deletions.
14 changes: 14 additions & 0 deletions library/std/src/io/buffered/bufreader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,20 @@ impl<R: Read> Read for BufReader<R> {
Ok(nread)
}

// Small read_exacts from a BufReader are extremely common when used with a deserializer.
// The default implementation calls read in a loop, which results in surprisingly poor code
// generation for the common path where the buffer has enough bytes to fill the passed-in
// buffer.
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
if self.buffer().len() >= buf.len() {
buf.copy_from_slice(&self.buffer()[..buf.len()]);
self.consume(buf.len());
return Ok(());
}

crate::io::default_read_exact(self, buf)
}

fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
let total_len = bufs.iter().map(|b| b.len()).sum::<usize>();
if self.pos == self.cap && total_len >= self.buf.len() {
Expand Down
12 changes: 12 additions & 0 deletions library/std/src/io/buffered/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,18 @@ fn bench_buffered_reader(b: &mut test::Bencher) {
b.iter(|| BufReader::new(io::empty()));
}

#[bench]
fn bench_buffered_reader_small_reads(b: &mut test::Bencher) {
let data = (0..u8::MAX).cycle().take(1024 * 4).collect::<Vec<_>>();
b.iter(|| {
let mut reader = BufReader::new(&data[..]);
let mut buf = [0u8; 4];
for _ in 0..1024 {
reader.read_exact(&mut buf).unwrap();
}
});
}

#[bench]
fn bench_buffered_writer(b: &mut test::Bencher) {
b.iter(|| BufWriter::new(io::sink()));
Expand Down
38 changes: 21 additions & 17 deletions library/std/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,25 @@ where
write(buf)
}

pub(crate) fn default_read_exact<R: Read + ?Sized>(this: &mut R, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match this.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
if !buf.is_empty() {
Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
} else {
Ok(())
}
}

/// The `Read` trait allows for reading bytes from a source.
///
/// Implementors of the `Read` trait are called 'readers'.
Expand Down Expand Up @@ -766,23 +785,8 @@ pub trait Read {
/// }
/// ```
#[stable(feature = "read_exact", since = "1.6.0")]
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<()> {
while !buf.is_empty() {
match self.read(buf) {
Ok(0) => break,
Ok(n) => {
let tmp = buf;
buf = &mut tmp[n..];
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
if !buf.is_empty() {
Err(Error::new(ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
} else {
Ok(())
}
fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
default_read_exact(self, buf)
}

/// Creates a "by reference" adaptor for this instance of `Read`.
Expand Down

0 comments on commit 1448a76

Please sign in to comment.