-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #112330 - the8472:use-buf-reader-buffer, r=thomcc
Extend io::copy buffer reuse to BufReader too previously it was only able to use BufWriter. This was due to a limitation in the BufReader generics that prevented specialization. This change works around the issue by using `BufReader where Self: Read` instead of `BufReader<I> where I: Read`. This limits our options, e.g. we can't access the inner reader, but it happens to work out if we rely on some implementation details. Copying 1MiB from `/dev/zero` to `/dev/null` through a 256KiB BufReader yields following improvements ``` OLD: io::copy::tests::bench_copy_buf_reader 51.44µs/iter +/- 703.00ns NEW: io::copy::tests::bench_copy_buf_reader 18.55µs/iter +/- 237.00ns ``` Previously this would read 256KiB into the reader but then copy 8KiB chunks to the writer through an additional intermediate buffer inside `io::copy`. Since those devices don't do much work most of the speedup should come from fewer syscalls and avoided memcopies. The b3sum crate [notes that the default buffer size in io::copy is too small](https://github.com/BLAKE3-team/BLAKE3/blob/4108923f5284e0f8c3cf97b59041c2b6b2f601d3/b3sum/src/main.rs#L235-L239). With this optimization they could achieve the desired performance by wrapping the reader in a `BufReader` instead of handrolling it. Currently the optimization doesn't apply to things like `StdinLock`, but this can be addressed with an additional `AsMutBufReader` specialization trait.
- Loading branch information
Showing
4 changed files
with
207 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use crate::cmp::{max, min}; | ||
use crate::io::*; | ||
|
||
#[test] | ||
fn copy_copies() { | ||
let mut r = repeat(0).take(4); | ||
let mut w = sink(); | ||
assert_eq!(copy(&mut r, &mut w).unwrap(), 4); | ||
|
||
let mut r = repeat(0).take(1 << 17); | ||
assert_eq!(copy(&mut r as &mut dyn Read, &mut w as &mut dyn Write).unwrap(), 1 << 17); | ||
} | ||
|
||
struct ShortReader { | ||
cap: usize, | ||
read_size: usize, | ||
observed_buffer: usize, | ||
} | ||
|
||
impl Read for ShortReader { | ||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> { | ||
let bytes = min(self.cap, self.read_size); | ||
self.cap -= bytes; | ||
self.observed_buffer = max(self.observed_buffer, buf.len()); | ||
Ok(bytes) | ||
} | ||
} | ||
|
||
struct WriteObserver { | ||
observed_buffer: usize, | ||
} | ||
|
||
impl Write for WriteObserver { | ||
fn write(&mut self, buf: &[u8]) -> Result<usize> { | ||
self.observed_buffer = max(self.observed_buffer, buf.len()); | ||
Ok(buf.len()) | ||
} | ||
|
||
fn flush(&mut self) -> Result<()> { | ||
Ok(()) | ||
} | ||
} | ||
|
||
#[test] | ||
fn copy_specializes_bufwriter() { | ||
let cap = 117 * 1024; | ||
let buf_sz = 16 * 1024; | ||
let mut r = ShortReader { cap, observed_buffer: 0, read_size: 1337 }; | ||
let mut w = BufWriter::with_capacity(buf_sz, WriteObserver { observed_buffer: 0 }); | ||
assert_eq!( | ||
copy(&mut r, &mut w).unwrap(), | ||
cap as u64, | ||
"expected the whole capacity to be copied" | ||
); | ||
assert_eq!(r.observed_buffer, buf_sz, "expected a large buffer to be provided to the reader"); | ||
assert!(w.get_mut().observed_buffer > DEFAULT_BUF_SIZE, "expected coalesced writes"); | ||
} | ||
|
||
#[test] | ||
fn copy_specializes_bufreader() { | ||
let mut source = vec![0; 768 * 1024]; | ||
source[1] = 42; | ||
let mut buffered = BufReader::with_capacity(256 * 1024, Cursor::new(&mut source)); | ||
|
||
let mut sink = Vec::new(); | ||
assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64); | ||
assert_eq!(source.as_slice(), sink.as_slice()); | ||
|
||
let buf_sz = 71 * 1024; | ||
assert!(buf_sz > DEFAULT_BUF_SIZE, "test precondition"); | ||
|
||
let mut buffered = BufReader::with_capacity(buf_sz, Cursor::new(&mut source)); | ||
let mut sink = WriteObserver { observed_buffer: 0 }; | ||
assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64); | ||
assert_eq!( | ||
sink.observed_buffer, buf_sz, | ||
"expected a large buffer to be provided to the writer" | ||
); | ||
} | ||
|
||
#[cfg(unix)] | ||
mod io_benches { | ||
use crate::fs::File; | ||
use crate::fs::OpenOptions; | ||
use crate::io::prelude::*; | ||
use crate::io::BufReader; | ||
|
||
use test::Bencher; | ||
|
||
#[bench] | ||
fn bench_copy_buf_reader(b: &mut Bencher) { | ||
let mut file_in = File::open("/dev/zero").expect("opening /dev/zero failed"); | ||
// use dyn to avoid specializations unrelated to readbuf | ||
let dyn_in = &mut file_in as &mut dyn Read; | ||
let mut reader = BufReader::with_capacity(256 * 1024, dyn_in.take(0)); | ||
let mut writer = | ||
OpenOptions::new().write(true).open("/dev/null").expect("opening /dev/null failed"); | ||
|
||
const BYTES: u64 = 1024 * 1024; | ||
|
||
b.bytes = BYTES; | ||
|
||
b.iter(|| { | ||
reader.get_mut().set_limit(BYTES); | ||
crate::io::copy(&mut reader, &mut writer).unwrap() | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters