Skip to content

Commit 7513407

Browse files
committed
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.
2 parents f90d57d + 3738785 commit 7513407

File tree

4 files changed

+207
-74
lines changed

4 files changed

+207
-74
lines changed

library/std/src/io/buffered/bufreader.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ impl<R: ?Sized> BufReader<R> {
222222

223223
/// Invalidates all data in the internal buffer.
224224
#[inline]
225-
fn discard_buffer(&mut self) {
225+
pub(in crate::io) fn discard_buffer(&mut self) {
226226
self.buf.discard_buffer()
227227
}
228228
}

library/std/src/io/copy.rs

+97-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use super::{BorrowedBuf, BufWriter, ErrorKind, Read, Result, Write, DEFAULT_BUF_SIZE};
1+
use super::{BorrowedBuf, BufReader, BufWriter, ErrorKind, Read, Result, Write, DEFAULT_BUF_SIZE};
22
use crate::mem::MaybeUninit;
33

4+
#[cfg(test)]
5+
mod tests;
6+
47
/// Copies the entire contents of a reader into a writer.
58
///
69
/// This function will continuously read data from `reader` and then
@@ -71,32 +74,113 @@ where
7174
R: Read,
7275
W: Write,
7376
{
74-
BufferedCopySpec::copy_to(reader, writer)
77+
let read_buf = BufferedReaderSpec::buffer_size(reader);
78+
let write_buf = BufferedWriterSpec::buffer_size(writer);
79+
80+
if read_buf >= DEFAULT_BUF_SIZE && read_buf >= write_buf {
81+
return BufferedReaderSpec::copy_to(reader, writer);
82+
}
83+
84+
BufferedWriterSpec::copy_from(writer, reader)
85+
}
86+
87+
/// Specialization of the read-write loop that reuses the internal
88+
/// buffer of a BufReader. If there's no buffer then the writer side
89+
/// should be used intead.
90+
trait BufferedReaderSpec {
91+
fn buffer_size(&self) -> usize;
92+
93+
fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result<u64>;
94+
}
95+
96+
impl<T> BufferedReaderSpec for T
97+
where
98+
Self: Read,
99+
T: ?Sized,
100+
{
101+
#[inline]
102+
default fn buffer_size(&self) -> usize {
103+
0
104+
}
105+
106+
default fn copy_to(&mut self, _to: &mut (impl Write + ?Sized)) -> Result<u64> {
107+
unimplemented!("only called from specializations");
108+
}
109+
}
110+
111+
impl<I> BufferedReaderSpec for BufReader<I>
112+
where
113+
Self: Read,
114+
I: ?Sized,
115+
{
116+
fn buffer_size(&self) -> usize {
117+
self.capacity()
118+
}
119+
120+
fn copy_to(&mut self, to: &mut (impl Write + ?Sized)) -> Result<u64> {
121+
let mut len = 0;
122+
123+
loop {
124+
// Hack: this relies on `impl Read for BufReader` always calling fill_buf
125+
// if the buffer is empty, even for empty slices.
126+
// It can't be called directly here since specialization prevents us
127+
// from adding I: Read
128+
match self.read(&mut []) {
129+
Ok(_) => {}
130+
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
131+
Err(e) => return Err(e),
132+
}
133+
let buf = self.buffer();
134+
if self.buffer().len() == 0 {
135+
return Ok(len);
136+
}
137+
138+
// In case the writer side is a BufWriter then its write_all
139+
// implements an optimization that passes through large
140+
// buffers to the underlying writer. That code path is #[cold]
141+
// but we're still avoiding redundant memcopies when doing
142+
// a copy between buffered inputs and outputs.
143+
to.write_all(buf)?;
144+
len += buf.len() as u64;
145+
self.discard_buffer();
146+
}
147+
}
75148
}
76149

77150
/// Specialization of the read-write loop that either uses a stack buffer
78151
/// or reuses the internal buffer of a BufWriter
79-
trait BufferedCopySpec: Write {
80-
fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64>;
152+
trait BufferedWriterSpec: Write {
153+
fn buffer_size(&self) -> usize;
154+
155+
fn copy_from<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<u64>;
81156
}
82157

83-
impl<W: Write + ?Sized> BufferedCopySpec for W {
84-
default fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64> {
85-
stack_buffer_copy(reader, writer)
158+
impl<W: Write + ?Sized> BufferedWriterSpec for W {
159+
#[inline]
160+
default fn buffer_size(&self) -> usize {
161+
0
162+
}
163+
164+
default fn copy_from<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<u64> {
165+
stack_buffer_copy(reader, self)
86166
}
87167
}
88168

89-
impl<I: ?Sized + Write> BufferedCopySpec for BufWriter<I> {
90-
fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64> {
91-
if writer.capacity() < DEFAULT_BUF_SIZE {
92-
return stack_buffer_copy(reader, writer);
169+
impl<I: Write + ?Sized> BufferedWriterSpec for BufWriter<I> {
170+
fn buffer_size(&self) -> usize {
171+
self.capacity()
172+
}
173+
174+
fn copy_from<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<u64> {
175+
if self.capacity() < DEFAULT_BUF_SIZE {
176+
return stack_buffer_copy(reader, self);
93177
}
94178

95179
let mut len = 0;
96180
let mut init = 0;
97181

98182
loop {
99-
let buf = writer.buffer_mut();
183+
let buf = self.buffer_mut();
100184
let mut read_buf: BorrowedBuf<'_> = buf.spare_capacity_mut().into();
101185

102186
unsafe {
@@ -127,7 +211,7 @@ impl<I: ?Sized + Write> BufferedCopySpec for BufWriter<I> {
127211
Err(e) => return Err(e),
128212
}
129213
} else {
130-
writer.flush_buf()?;
214+
self.flush_buf()?;
131215
init = 0;
132216
}
133217
}

library/std/src/io/copy/tests.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::cmp::{max, min};
2+
use crate::io::*;
3+
4+
#[test]
5+
fn copy_copies() {
6+
let mut r = repeat(0).take(4);
7+
let mut w = sink();
8+
assert_eq!(copy(&mut r, &mut w).unwrap(), 4);
9+
10+
let mut r = repeat(0).take(1 << 17);
11+
assert_eq!(copy(&mut r as &mut dyn Read, &mut w as &mut dyn Write).unwrap(), 1 << 17);
12+
}
13+
14+
struct ShortReader {
15+
cap: usize,
16+
read_size: usize,
17+
observed_buffer: usize,
18+
}
19+
20+
impl Read for ShortReader {
21+
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
22+
let bytes = min(self.cap, self.read_size);
23+
self.cap -= bytes;
24+
self.observed_buffer = max(self.observed_buffer, buf.len());
25+
Ok(bytes)
26+
}
27+
}
28+
29+
struct WriteObserver {
30+
observed_buffer: usize,
31+
}
32+
33+
impl Write for WriteObserver {
34+
fn write(&mut self, buf: &[u8]) -> Result<usize> {
35+
self.observed_buffer = max(self.observed_buffer, buf.len());
36+
Ok(buf.len())
37+
}
38+
39+
fn flush(&mut self) -> Result<()> {
40+
Ok(())
41+
}
42+
}
43+
44+
#[test]
45+
fn copy_specializes_bufwriter() {
46+
let cap = 117 * 1024;
47+
let buf_sz = 16 * 1024;
48+
let mut r = ShortReader { cap, observed_buffer: 0, read_size: 1337 };
49+
let mut w = BufWriter::with_capacity(buf_sz, WriteObserver { observed_buffer: 0 });
50+
assert_eq!(
51+
copy(&mut r, &mut w).unwrap(),
52+
cap as u64,
53+
"expected the whole capacity to be copied"
54+
);
55+
assert_eq!(r.observed_buffer, buf_sz, "expected a large buffer to be provided to the reader");
56+
assert!(w.get_mut().observed_buffer > DEFAULT_BUF_SIZE, "expected coalesced writes");
57+
}
58+
59+
#[test]
60+
fn copy_specializes_bufreader() {
61+
let mut source = vec![0; 768 * 1024];
62+
source[1] = 42;
63+
let mut buffered = BufReader::with_capacity(256 * 1024, Cursor::new(&mut source));
64+
65+
let mut sink = Vec::new();
66+
assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64);
67+
assert_eq!(source.as_slice(), sink.as_slice());
68+
69+
let buf_sz = 71 * 1024;
70+
assert!(buf_sz > DEFAULT_BUF_SIZE, "test precondition");
71+
72+
let mut buffered = BufReader::with_capacity(buf_sz, Cursor::new(&mut source));
73+
let mut sink = WriteObserver { observed_buffer: 0 };
74+
assert_eq!(crate::io::copy(&mut buffered, &mut sink).unwrap(), source.len() as u64);
75+
assert_eq!(
76+
sink.observed_buffer, buf_sz,
77+
"expected a large buffer to be provided to the writer"
78+
);
79+
}
80+
81+
#[cfg(unix)]
82+
mod io_benches {
83+
use crate::fs::File;
84+
use crate::fs::OpenOptions;
85+
use crate::io::prelude::*;
86+
use crate::io::BufReader;
87+
88+
use test::Bencher;
89+
90+
#[bench]
91+
fn bench_copy_buf_reader(b: &mut Bencher) {
92+
let mut file_in = File::open("/dev/zero").expect("opening /dev/zero failed");
93+
// use dyn to avoid specializations unrelated to readbuf
94+
let dyn_in = &mut file_in as &mut dyn Read;
95+
let mut reader = BufReader::with_capacity(256 * 1024, dyn_in.take(0));
96+
let mut writer =
97+
OpenOptions::new().write(true).open("/dev/null").expect("opening /dev/null failed");
98+
99+
const BYTES: u64 = 1024 * 1024;
100+
101+
b.bytes = BYTES;
102+
103+
b.iter(|| {
104+
reader.get_mut().set_limit(BYTES);
105+
crate::io::copy(&mut reader, &mut writer).unwrap()
106+
});
107+
}
108+
}

library/std/src/io/util/tests.rs

+1-60
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,8 @@
1-
use crate::cmp::{max, min};
21
use crate::io::prelude::*;
3-
use crate::io::{
4-
copy, empty, repeat, sink, BorrowedBuf, BufWriter, Empty, Repeat, Result, SeekFrom, Sink,
5-
DEFAULT_BUF_SIZE,
6-
};
2+
use crate::io::{empty, repeat, sink, BorrowedBuf, Empty, Repeat, SeekFrom, Sink};
73

84
use crate::mem::MaybeUninit;
95

10-
#[test]
11-
fn copy_copies() {
12-
let mut r = repeat(0).take(4);
13-
let mut w = sink();
14-
assert_eq!(copy(&mut r, &mut w).unwrap(), 4);
15-
16-
let mut r = repeat(0).take(1 << 17);
17-
assert_eq!(copy(&mut r as &mut dyn Read, &mut w as &mut dyn Write).unwrap(), 1 << 17);
18-
}
19-
20-
struct ShortReader {
21-
cap: usize,
22-
read_size: usize,
23-
observed_buffer: usize,
24-
}
25-
26-
impl Read for ShortReader {
27-
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
28-
let bytes = min(self.cap, self.read_size);
29-
self.cap -= bytes;
30-
self.observed_buffer = max(self.observed_buffer, buf.len());
31-
Ok(bytes)
32-
}
33-
}
34-
35-
struct WriteObserver {
36-
observed_buffer: usize,
37-
}
38-
39-
impl Write for WriteObserver {
40-
fn write(&mut self, buf: &[u8]) -> Result<usize> {
41-
self.observed_buffer = max(self.observed_buffer, buf.len());
42-
Ok(buf.len())
43-
}
44-
45-
fn flush(&mut self) -> Result<()> {
46-
Ok(())
47-
}
48-
}
49-
50-
#[test]
51-
fn copy_specializes_bufwriter() {
52-
let cap = 117 * 1024;
53-
let buf_sz = 16 * 1024;
54-
let mut r = ShortReader { cap, observed_buffer: 0, read_size: 1337 };
55-
let mut w = BufWriter::with_capacity(buf_sz, WriteObserver { observed_buffer: 0 });
56-
assert_eq!(
57-
copy(&mut r, &mut w).unwrap(),
58-
cap as u64,
59-
"expected the whole capacity to be copied"
60-
);
61-
assert_eq!(r.observed_buffer, buf_sz, "expected a large buffer to be provided to the reader");
62-
assert!(w.get_mut().observed_buffer > DEFAULT_BUF_SIZE, "expected coalesced writes");
63-
}
64-
656
#[test]
667
fn sink_sinks() {
678
let mut s = sink();

0 commit comments

Comments
 (0)