Change:
fn copy_from<R: Read + ?Sized>(&mut self, reader: &mut R) -> Result<u64> { let mut bytes = 0; <del> // avoid allocating before we have determined that there's anything to read if self.capacity() == 0 { bytes = stack_buffer_copy(&mut reader.take(DEFAULT_BUF_SIZE as u64), self)?; if bytes == 0 { return Ok(0); </del> <ins> // avoid inflating empty/small vecs before we have determined that there's anything to read if self.capacity() < DEFAULT_BUF_SIZE { let stack_read_limit = DEFAULT_BUF_SIZE as u64; bytes = stack_buffer_copy(&mut reader.take(stack_read_limit), self)?; // fewer bytes than requested -> EOF reached if bytes < stack_read_limit { return Ok(bytes); </ins> } } <ins> // don't immediately offer the vec's whole spare capacity, otherwise // we might have to fully initialize it if the reader doesn't have a custom read_buf() impl let mut max_read_size = DEFAULT_BUF_SIZE; </ins> loop { self.reserve(DEFAULT_BUF_SIZE); <del> let mut buf: BorrowedBuf<'_> = self.spare_capacity_mut().into(); match reader.read_buf(buf.unfilled()) { Ok(()) => {} Err(e) if e.is_interrupted() => continue, Err(e) => return Err(e), }; </del> <ins> let mut initialized_spare_capacity = 0; </ins> <del> let read = buf.filled().len(); if read == 0 { break; } </del> <ins> loop { let buf = self.spare_capacity_mut(); let read_size = min(max_read_size, buf.len()); let mut buf = BorrowedBuf::from(&mut buf[..read_size]); // SAFETY: init is either 0 or the init_len from the previous iteration. unsafe { buf.set_init(initialized_spare_capacity); } match reader.read_buf(buf.unfilled()) { Ok(()) => { let bytes_read = buf.len(); </ins> <del> // SAFETY: BorrowedBuf guarantees all of its filled bytes are init // and the number of read bytes can't exceed the spare capacity since // that's what the buffer is borrowing from. unsafe { self.set_len(self.len() + read) }; bytes += read as u64; } </del> <ins> // EOF if bytes_read == 0 { return Ok(bytes); } </ins> <del> Ok(bytes) </del> <ins> // the reader is returning short reads but it doesn't call ensure_init() if buf.init_len() < buf.capacity() { max_read_size = usize::MAX; } // the reader hasn't returned short reads so far if bytes_read == buf.capacity() { max_read_size *= 2; } initialized_spare_capacity = buf.init_len() - bytes_read; bytes += bytes_read as u64; // SAFETY: BorrowedBuf guarantees all of its filled bytes are init // and the number of read bytes can't exceed the spare capacity since // that's what the buffer is borrowing from. unsafe { self.set_len(self.len() + bytes_read) }; // spare capacity full, reserve more if self.len() == self.capacity() { break; } } Err(e) if e.is_interrupted() => continue, Err(e) => return Err(e), } } } </ins> } }

Discussion:
Reported by a lz4flex user I created a small repo to demonstrate on a minimal example. BorrowedCursor::ensureinit from this PR seems exhibit exponential cost. The program creates a Vec and reads it in chunks of 32kb. Size is specified as a command line argument (100MB-400MB). (If your default is 1.73) <!-- If you know when this regression occurred, please add a line like below, replacing with one of stable, beta, or nightly. modify labels: +regression-from-stable-to-stable -regression-untriaged --<!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOT_END --
Maybe try (tutorial: https://rust-) to find which PR caused this?
Really nice bisect tool. From the commit list in that Auto merge it seems to be
cc author
The loop creates a new on each iteration, which is fine if the reader can fill the provided buffer in one go but does unnecessary work on short reads. Should be easy to fix. claim
Any chance of getting this fix in a backport to 1.73?