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: FWIW, I'm providing the benchmark code below; it can easily be expanded to different file sizes:
4MB isn’t a whole lot. That’s well within the size of hard drive caches or possibly an OS memory-based cache. I was noticing the difference with files several gigabytes in size. As a sanity check, maybe look to see what the actual time is, not just the % difference between timings with different buffer sizes. The actual time may be wildly different than the actual steady state performance of the disk if it’s reading the data from a cache. It may also be possible at a low level to pass a flag to encourage the OS to disable or flush the cache, but I don’t know if there’s any way to instruct the hard drive to do the same.
I only pasted the benchmark for 4MB as a reference - I tested files from 4KB up to 16MB. I'm not an expert with low-level hard drive stuff, just hoping my attempt can help someone else.
Ideally you would test with a file approximately twice as big as you have RAM, so if you have 8GB of RAM you would want to test with a 16GB file.
extended open flags are accessible via FWIW lots of bennchmarks exist showing synchronous IO calls improve when matching IO operations to the underlying block size (e.g. 4MB for modern SSD's) across multiple OS's, so its very likely that there are improvements to gain here. However, 4MB is stack-breaking size by default, and heap allocations will destroy potential improvements (and further, if we're optimising, we probably want overlapped IO and a ring of buffers rather than just one buffer which will always leave one device idle).... so its complex;)
heap allocations will destroy potential improvements How come? would allocate once at the beginning. Heap memory isn't inherently slower and one memory allocation is basically free compared to the heavy IO that's happening afterwards.
But we also don't want to be allocating 4MB every time we do as someone might be doing a bunch of small copies.