Skip to content

Commit fe42003

Browse files
authored
Rollup merge of #102760 - saethlin:dont-reinit-buffer, r=Mark-Simulacrum
Avoid repeated re-initialization of the BufReader buffer Fixes #102727 We accidentally removed this in #98748. It looks so redundant. But it isn't. The default `Read::read_buf` will defensively initialize the whole buffer, if any of it is indicated to be uninitialized. In uses where reads from the wrapped `Read` impl completely fill the `BufReader`, `initialized` and `filled` are the same, and this extra member isn't required. But in the reported issue, the `BufReader` wraps a `Read` impl which will _never_ fill the whole buffer. So the default `Read::read_buf` implementation repeatedly re-initializes the extra space in the buffer. This adds back the extra `initialized` member, which ensures that the default `Read::read_buf` only zero-initialized the buffer once, and I've tried to add a comment which explains this whole situation.
2 parents e461e94 + 95ae993 commit fe42003

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@ impl<R> BufReader<R> {
224224
}
225225
}
226226

227+
// This is only used by a test which asserts that the initialization-tracking is correct.
228+
#[cfg(test)]
229+
impl<R> BufReader<R> {
230+
pub fn initialized(&self) -> usize {
231+
self.buf.initialized()
232+
}
233+
}
234+
227235
impl<R: Seek> BufReader<R> {
228236
/// Seeks relative to the current position. If the new position lies within the buffer,
229237
/// the buffer will not be flushed, allowing for more efficient seeks.

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ pub struct Buffer {
2020
// Each call to `fill_buf` sets `filled` to indicate how many bytes at the start of `buf` are
2121
// initialized with bytes from a read.
2222
filled: usize,
23+
// This is the max number of bytes returned across all `fill_buf` calls. We track this so that we
24+
// can accurately tell `read_buf` how many bytes of buf are initialized, to bypass as much of its
25+
// defensive initialization as possible. Note that while this often the same as `filled`, it
26+
// doesn't need to be. Calls to `fill_buf` are not required to actually fill the buffer, and
27+
// omitting this is a huge perf regression for `Read` impls that do not.
28+
initialized: usize,
2329
}
2430

2531
impl Buffer {
2632
#[inline]
2733
pub fn with_capacity(capacity: usize) -> Self {
2834
let buf = Box::new_uninit_slice(capacity);
29-
Self { buf, pos: 0, filled: 0 }
35+
Self { buf, pos: 0, filled: 0, initialized: 0 }
3036
}
3137

3238
#[inline]
@@ -51,6 +57,12 @@ impl Buffer {
5157
self.pos
5258
}
5359

60+
// This is only used by a test which asserts that the initialization-tracking is correct.
61+
#[cfg(test)]
62+
pub fn initialized(&self) -> usize {
63+
self.initialized
64+
}
65+
5466
#[inline]
5567
pub fn discard_buffer(&mut self) {
5668
self.pos = 0;
@@ -96,13 +108,14 @@ impl Buffer {
96108
let mut buf = BorrowedBuf::from(&mut *self.buf);
97109
// SAFETY: `self.filled` bytes will always have been initialized.
98110
unsafe {
99-
buf.set_init(self.filled);
111+
buf.set_init(self.initialized);
100112
}
101113

102114
reader.read_buf(buf.unfilled())?;
103115

104-
self.filled = buf.len();
105116
self.pos = 0;
117+
self.filled = buf.len();
118+
self.initialized = buf.init_len();
106119
}
107120
Ok(self.buffer())
108121
}

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

+24
Original file line numberDiff line numberDiff line change
@@ -1039,3 +1039,27 @@ fn single_formatted_write() {
10391039
writeln!(&mut writer, "{}, {}!", "hello", "world").unwrap();
10401040
assert_eq!(writer.get_ref().events, [RecordedEvent::Write("hello, world!\n".to_string())]);
10411041
}
1042+
1043+
#[test]
1044+
fn bufreader_full_initialize() {
1045+
struct OneByteReader;
1046+
impl Read for OneByteReader {
1047+
fn read(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
1048+
if buf.len() > 0 {
1049+
buf[0] = 0;
1050+
Ok(1)
1051+
} else {
1052+
Ok(0)
1053+
}
1054+
}
1055+
}
1056+
let mut reader = BufReader::new(OneByteReader);
1057+
// Nothing is initialized yet.
1058+
assert_eq!(reader.initialized(), 0);
1059+
1060+
let buf = reader.fill_buf().unwrap();
1061+
// We read one byte...
1062+
assert_eq!(buf.len(), 1);
1063+
// But we initialized the whole buffer!
1064+
assert_eq!(reader.initialized(), reader.capacity());
1065+
}

0 commit comments

Comments
 (0)