Code change:
} } <del> /// The general read-write-loop implementation of /// `io::copy` that is used when specializations are not available or not applicable. pub(crate) fn generic_copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> io::Result<u64> </del> <ins> /// The userspace read-write-loop implementation of `io::copy` that is used when /// OS-specific specializations for copy offloading are not available or not applicable. pub(crate) fn generic_copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> Result<u64> </ins> where R: Read, W: Write, { <del> let mut buf = MaybeUninit::<[u8; super::DEFAULT_BUF_SIZE]>::uninit(); </del> <ins> BufferedCopySpec::copy_to(reader, writer) } /// Specialization of the read-write loop that either uses a stack buffer /// or reuses the internal buffer of a BufWriter trait BufferedCopySpec: Write { fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64>; } impl<W: Write + ?Sized> BufferedCopySpec for W { default fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64> { stack_buffer_copy(reader, writer) } } impl<I: Write> BufferedCopySpec for BufWriter<I> { fn copy_to<R: Read + ?Sized>(reader: &mut R, writer: &mut Self) -> Result<u64> { if writer.capacity() < DEFAULT_BUF_SIZE { return stack_buffer_copy(reader, writer); } // FIXME: #42788 // //   - This creates a (mut) reference to a slice of //     _uninitialized_ integers, which is **undefined behavior** // //   - Only the standard library gets to soundly "ignore" this, //     based on its privileged knowledge of unstable rustc //     internals; unsafe { let spare_cap = writer.buffer_mut().spare_capacity_mut(); reader.initializer().initialize(MaybeUninit::slice_assume_init_mut(spare_cap)); } let mut len = 0; loop { let buf = writer.buffer_mut(); let spare_cap = buf.spare_capacity_mut(); if spare_cap.len() >= DEFAULT_BUF_SIZE { match reader.read(unsafe { MaybeUninit::slice_assume_init_mut(spare_cap) }) { Ok(0) => return Ok(len), // EOF reached Ok(bytes_read) => { assert!(bytes_read <= spare_cap.len()); // Safety: The initializer contract guarantees that either it or `read` // will have initialized these bytes. And we just checked that the number // of bytes is within the buffer capacity. unsafe { buf.set_len(buf.len() + bytes_read) }; len += bytes_read as u64; // Read again if the buffer still has enough capacity, as BufWriter itself would do // This will occur if the reader returns short reads continue; } Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(e) => return Err(e), } } writer.flush_buf()?; } } } fn stack_buffer_copy<R: Read + ?Sized, W: Write + ?Sized>( reader: &mut R, writer: &mut W, ) -> Result<u64> { let mut buf = MaybeUninit::<[u8; DEFAULT_BUF_SIZE]>::uninit(); </ins> // FIXME: #42788 // //   - This creates a (mut) reference to a slice of

Related discussion:
text: possible approaches: thread local buffers or a buffer pool stat the input before deciding on the buffer size (this is what gnu cp does I think) heap-allocate a bigger buffer after a few iterations of the copy loop with an on-stack buffer specialize copy operations from file to memory to directly into the already allocated target, thus skipping the need for a buffer
specialize copy operations from file to memory to directly into the already allocated target, thus skipping the need for a buffer I think some kind of specialization for sources would be ideal. Because in this case, should not buffer at all and simply use the reader's buffer. Effectively this also gives you a customizable buffer size by wrapping your in a with whatever size you want. As of today, if your source is then is always the wrong answer. I got a substantial performance improvement using a simple loop instead:
a single heap allocation is entirely reasonable for a single invocation of copy of a lot of data. For many copies of a small amount of data, one ends up with a lot of allocations again (consider for instance copying data out of a tar, where we have views on the data and there's no actual synchronous IO etc happening. I like some of the suggested possibilities with specialisation.
It's a nice general optimization, but not necesarily ideal if you use a as . Adding a on the reader size does let you control the buffer size but it still introduces an unnecessary memcopy compared to . Perhaps a writer-side optimization would be more flexible. Then you can specialize either for or
That sounds rather uncommon to me though. After all, is provided by the trait so it's always available. The only case I can see where you would invoke with a as destination is some generic interfaces that takes any . But IMO such interfaces are ill-designed, as they could simply implement instead and offer a much more flexible API.
yes, generic code taking a write was the concern. E.g. tar-rs has a tar archive which takes a and is fed with items. I'm not saying that creating an in-memory archive would be a good idea, just pointing out that these patterns do exist.
I had same kind of issue but with different library (). The copy rate was around 70 MB/s (inside single NVMe SSD). The problem was actually with running the debug version of my program.