BufWriter: avoid using expensive Vec methods
We use a Vec as our internal, constant-sized buffer, but the overhead of using methods like `extend_from_slice` can be enormous, likely because they don't get inlined, because `Vec` has to repeat bounds checks that we've already done, and because it makes considerations for things like reallocating, even though they should never happen.
This commit is contained in:
parent
1f32d40ac3
commit
b43e8e248b
1 changed files with 75 additions and 12 deletions
|
@ -4,6 +4,7 @@ use crate::io::{
|
||||||
self, Error, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, DEFAULT_BUF_SIZE,
|
self, Error, ErrorKind, IntoInnerError, IoSlice, Seek, SeekFrom, Write, DEFAULT_BUF_SIZE,
|
||||||
};
|
};
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
|
use crate::ptr;
|
||||||
|
|
||||||
/// Wraps a writer and buffers its output.
|
/// Wraps a writer and buffers its output.
|
||||||
///
|
///
|
||||||
|
@ -68,6 +69,10 @@ use crate::mem;
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub struct BufWriter<W: Write> {
|
pub struct BufWriter<W: Write> {
|
||||||
inner: Option<W>,
|
inner: Option<W>,
|
||||||
|
// The buffer. Avoid using this like a normal `Vec` in common code paths.
|
||||||
|
// That is, don't use `buf.push`, `buf.extend_from_slice`, or any other
|
||||||
|
// methods that require bounds checking or the like. This makes an enormous
|
||||||
|
// difference to performance (we may want to stop using a `Vec` entirely).
|
||||||
buf: Vec<u8>,
|
buf: Vec<u8>,
|
||||||
// #30888: If the inner writer panics in a call to write, we don't want to
|
// #30888: If the inner writer panics in a call to write, we don't want to
|
||||||
// write the buffered data a second time in BufWriter's destructor. This
|
// write the buffered data a second time in BufWriter's destructor. This
|
||||||
|
@ -150,7 +155,11 @@ impl<W: Write> BufWriter<W> {
|
||||||
impl Drop for BufGuard<'_> {
|
impl Drop for BufGuard<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.written > 0 {
|
if self.written > 0 {
|
||||||
self.buffer.drain(..self.written);
|
if self.done() {
|
||||||
|
self.buffer.clear();
|
||||||
|
} else {
|
||||||
|
self.buffer.drain(..self.written);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,7 +192,12 @@ impl<W: Write> BufWriter<W> {
|
||||||
pub(super) fn write_to_buf(&mut self, buf: &[u8]) -> usize {
|
pub(super) fn write_to_buf(&mut self, buf: &[u8]) -> usize {
|
||||||
let available = self.buf.capacity() - self.buf.len();
|
let available = self.buf.capacity() - self.buf.len();
|
||||||
let amt_to_buffer = available.min(buf.len());
|
let amt_to_buffer = available.min(buf.len());
|
||||||
self.buf.extend_from_slice(&buf[..amt_to_buffer]);
|
|
||||||
|
// SAFETY: `amt_to_buffer` is <= buffer's spare capacity by construction.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(&buf[..amt_to_buffer]);
|
||||||
|
}
|
||||||
|
|
||||||
amt_to_buffer
|
amt_to_buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +362,13 @@ impl<W: Write> BufWriter<W> {
|
||||||
self.panicked = false;
|
self.panicked = false;
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
self.buf.extend_from_slice(buf);
|
// SAFETY: We just called `self.flush_buf()`, so `self.buf.len()` is 0, and
|
||||||
|
// we entered this else block because `buf.len() < self.buf.capacity()`.
|
||||||
|
// Therefore, `self.buf.len() + buf.len() <= self.buf.capacity()`.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,10 +393,29 @@ impl<W: Write> BufWriter<W> {
|
||||||
self.panicked = false;
|
self.panicked = false;
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
self.buf.extend_from_slice(buf);
|
// SAFETY: We just called `self.flush_buf()`, so `self.buf.len()` is 0, and
|
||||||
|
// we entered this else block because `buf.len() < self.buf.capacity()`.
|
||||||
|
// Therefore, `self.buf.len() + buf.len() <= self.buf.capacity()`.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: Requires `self.buf.len() + buf.len() <= self.buf.capacity()`,
|
||||||
|
// i.e., that input buffer length is less than or equal to spare capacity.
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn write_to_buffer_unchecked(&mut self, buf: &[u8]) {
|
||||||
|
debug_assert!(self.buf.len() + buf.len() <= self.buf.capacity());
|
||||||
|
let old_len = self.buf.len();
|
||||||
|
let buf_len = buf.len();
|
||||||
|
let src = buf.as_ptr();
|
||||||
|
let dst = self.buf.as_mut_ptr().add(old_len);
|
||||||
|
ptr::copy_nonoverlapping(src, dst, buf_len);
|
||||||
|
self.buf.set_len(old_len + buf_len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unstable(feature = "bufwriter_into_raw_parts", issue = "80690")]
|
#[unstable(feature = "bufwriter_into_raw_parts", issue = "80690")]
|
||||||
|
@ -456,7 +495,11 @@ impl<W: Write> Write for BufWriter<W> {
|
||||||
// prevents pathological cases for other clients which *always* make writes of this size.
|
// prevents pathological cases for other clients which *always* make writes of this size.
|
||||||
// See #72919 and #79930 for more info and a breadcrumb trail.
|
// See #72919 and #79930 for more info and a breadcrumb trail.
|
||||||
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
|
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
|
||||||
self.buf.extend_from_slice(buf);
|
// SAFETY: safe by above conditional.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
} else {
|
} else {
|
||||||
self.flush_and_write(buf)
|
self.flush_and_write(buf)
|
||||||
|
@ -471,7 +514,11 @@ impl<W: Write> Write for BufWriter<W> {
|
||||||
// prevents pathological cases for other clients which *always* make writes of this size.
|
// prevents pathological cases for other clients which *always* make writes of this size.
|
||||||
// See #72919 and #79930 for more info and a breadcrumb trail.
|
// See #72919 and #79930 for more info and a breadcrumb trail.
|
||||||
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
|
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
|
||||||
self.buf.extend_from_slice(buf);
|
// SAFETY: safe by above conditional.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.flush_and_write_all(buf)
|
self.flush_and_write_all(buf)
|
||||||
|
@ -492,7 +539,13 @@ impl<W: Write> Write for BufWriter<W> {
|
||||||
self.panicked = false;
|
self.panicked = false;
|
||||||
r
|
r
|
||||||
} else {
|
} else {
|
||||||
bufs.iter().for_each(|b| self.buf.extend_from_slice(b));
|
// SAFETY: We checked whether or not the spare capacity was large enough above. If
|
||||||
|
// it was, then we're safe already. If it wasn't, we flushed, making sufficient
|
||||||
|
// room for any input <= the buffer size, which includes this input.
|
||||||
|
unsafe {
|
||||||
|
bufs.iter().for_each(|b| self.write_to_buffer_unchecked(b));
|
||||||
|
};
|
||||||
|
|
||||||
Ok(total_len)
|
Ok(total_len)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -511,7 +564,13 @@ impl<W: Write> Write for BufWriter<W> {
|
||||||
self.panicked = false;
|
self.panicked = false;
|
||||||
return r;
|
return r;
|
||||||
} else {
|
} else {
|
||||||
self.buf.extend_from_slice(buf);
|
// SAFETY: We checked whether or not the spare capacity was large enough above.
|
||||||
|
// If it was, then we're safe already. If it wasn't, we flushed, making
|
||||||
|
// sufficient room for any input <= the buffer size, which includes this input.
|
||||||
|
unsafe {
|
||||||
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
buf.len()
|
buf.len()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -519,11 +578,15 @@ impl<W: Write> Write for BufWriter<W> {
|
||||||
};
|
};
|
||||||
debug_assert!(total_written != 0);
|
debug_assert!(total_written != 0);
|
||||||
for buf in iter {
|
for buf in iter {
|
||||||
if self.buf.len() + buf.len() > self.buf.capacity() {
|
if self.buf.len() + buf.len() <= self.buf.capacity() {
|
||||||
break;
|
// SAFETY: safe by above conditional.
|
||||||
} else {
|
unsafe {
|
||||||
self.buf.extend_from_slice(buf);
|
self.write_to_buffer_unchecked(buf);
|
||||||
|
}
|
||||||
|
|
||||||
total_written += buf.len();
|
total_written += buf.len();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(total_written)
|
Ok(total_written)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue