Skip to content

Commit 5fd9372

Browse files
committed
BufWriter: optimize for write sizes less than buffer size
Optimize for the common case where the input write size is less than the buffer size. This slightly increases the cost for pathological write patterns that commonly fill the buffer exactly, but if a client is doing that frequently, they're already paying the cost of frequent flushing, etc., so the cost is of this optimization to them is relatively small.
1 parent b43e8e2 commit 5fd9372

File tree

1 file changed

+32
-24
lines changed

1 file changed

+32
-24
lines changed

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

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -349,19 +349,26 @@ impl<W: Write> BufWriter<W> {
349349
// Ensure this function does not get inlined into `write`, so that it
350350
// remains inlineable and its common path remains as short as possible.
351351
// If this function ends up being called frequently relative to `write`,
352-
// it's likely a sign that the client is using an improperly sized buffer.
352+
// it's likely a sign that the client is using an improperly sized buffer
353+
// or their write patterns are somewhat pathological.
353354
#[inline(never)]
354-
fn flush_and_write(&mut self, buf: &[u8]) -> io::Result<usize> {
355-
self.flush_buf()?;
355+
fn write_cold(&mut self, buf: &[u8]) -> io::Result<usize> {
356+
if self.buf.len() + buf.len() > self.buf.capacity() {
357+
self.flush_buf()?;
358+
}
356359

357-
// Why not len > capacity? To avoid a needless trip through the buffer when the
358-
// input exactly fills. We'd just need to flush it to the underlying writer anyway.
360+
// Why not len > capacity? To avoid a needless trip through the buffer when the input
361+
// exactly fills it. We'd just need to flush it to the underlying writer anyway.
359362
if buf.len() >= self.buf.capacity() {
360363
self.panicked = true;
361364
let r = self.get_mut().write(buf);
362365
self.panicked = false;
363366
r
364367
} else {
368+
// Write to the buffer. In this case, we write to the buffer even if it fills it
369+
// exactly. Doing otherwise would mean flushing the buffer, then writing this
370+
// input to the inner writer, which in many cases would be a worse strategy.
371+
365372
// SAFETY: We just called `self.flush_buf()`, so `self.buf.len()` is 0, and
366373
// we entered this else block because `buf.len() < self.buf.capacity()`.
367374
// Therefore, `self.buf.len() + buf.len() <= self.buf.capacity()`.
@@ -376,23 +383,30 @@ impl<W: Write> BufWriter<W> {
376383
// Ensure this function does not get inlined into `write_all`, so that it
377384
// remains inlineable and its common path remains as short as possible.
378385
// If this function ends up being called frequently relative to `write_all`,
379-
// it's likely a sign that the client is using an improperly sized buffer.
386+
// it's likely a sign that the client is using an improperly sized buffer
387+
// or their write patterns are somewhat pathological.
380388
#[inline(never)]
381-
fn flush_and_write_all(&mut self, buf: &[u8]) -> io::Result<()> {
389+
fn write_all_cold(&mut self, buf: &[u8]) -> io::Result<()> {
382390
// Normally, `write_all` just calls `write` in a loop. We can do better
383391
// by calling `self.get_mut().write_all()` directly, which avoids
384392
// round trips through the buffer in the event of a series of partial
385393
// writes in some circumstances.
386-
self.flush_buf()?;
394+
if self.buf.len() + buf.len() > self.buf.capacity() {
395+
self.flush_buf()?;
396+
}
387397

388-
// Why not len > capacity? To avoid a needless trip through the buffer when the
389-
// input exactly fills. We'd just need to flush it to the underlying writer anyway.
398+
// Why not len > capacity? To avoid a needless trip through the buffer when the input
399+
// exactly fills it. We'd just need to flush it to the underlying writer anyway.
390400
if buf.len() >= self.buf.capacity() {
391401
self.panicked = true;
392402
let r = self.get_mut().write_all(buf);
393403
self.panicked = false;
394404
r
395405
} else {
406+
// Write to the buffer. In this case, we write to the buffer even if it fills it
407+
// exactly. Doing otherwise would mean flushing the buffer, then writing this
408+
// input to the inner writer, which in many cases would be a worse strategy.
409+
396410
// SAFETY: We just called `self.flush_buf()`, so `self.buf.len()` is 0, and
397411
// we entered this else block because `buf.len() < self.buf.capacity()`.
398412
// Therefore, `self.buf.len() + buf.len() <= self.buf.capacity()`.
@@ -489,39 +503,33 @@ impl fmt::Debug for WriterPanicked {
489503
impl<W: Write> Write for BufWriter<W> {
490504
#[inline]
491505
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
492-
// The `buf.len() != self.buf.capacity()` check is done to avoid a needless trip through
493-
// the buffer when the input is exactly the same size as it. For many clients, that is a
494-
// rare event, so it's unfortunate that the check is in the common code path. But it
495-
// prevents pathological cases for other clients which *always* make writes of this size.
496-
// See #72919 and #79930 for more info and a breadcrumb trail.
497-
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
506+
// Use < instead of <= to avoid a needless trip through the buffer in some cases.
507+
// See `write_cold` for details.
508+
if self.buf.len() + buf.len() < self.buf.capacity() {
498509
// SAFETY: safe by above conditional.
499510
unsafe {
500511
self.write_to_buffer_unchecked(buf);
501512
}
502513

503514
Ok(buf.len())
504515
} else {
505-
self.flush_and_write(buf)
516+
self.write_cold(buf)
506517
}
507518
}
508519

509520
#[inline]
510521
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
511-
// The `buf.len() != self.buf.capacity()` check is done to avoid a needless trip through
512-
// the buffer when the input is exactly the same size as it. For many clients, that is a
513-
// rare event, so it's unfortunate that the check is in the common code path. But it
514-
// prevents pathological cases for other clients which *always* make writes of this size.
515-
// See #72919 and #79930 for more info and a breadcrumb trail.
516-
if self.buf.len() + buf.len() <= self.buf.capacity() && buf.len() != self.buf.capacity() {
522+
// Use < instead of <= to avoid a needless trip through the buffer in some cases.
523+
// See `write_all_cold` for details.
524+
if self.buf.len() + buf.len() < self.buf.capacity() {
517525
// SAFETY: safe by above conditional.
518526
unsafe {
519527
self.write_to_buffer_unchecked(buf);
520528
}
521529

522530
Ok(())
523531
} else {
524-
self.flush_and_write_all(buf)
532+
self.write_all_cold(buf)
525533
}
526534
}
527535

0 commit comments

Comments
 (0)