From 3ff85c0225479aa337c43013075633ebb50353d2 Mon Sep 17 00:00:00 2001 From: relative23 <62724298+relative23@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:41:40 +0200 Subject: [PATCH 1/2] dd: write complete blocks without buffering them first BufferedOutput::write_blocks copies the incoming bytes into its internal buffer, hands that buffer to the inner writer, clears it, and then re-buffers the trailing partial block -- even when nothing was pending and the incoming bytes already form complete blocks. That is the common case: the copy loop hands over a multiple of the output block size on every iteration, so each one paid for a full memcpy of the block. Take that case directly: with an empty internal buffer, write the complete blocks straight from the caller's slice and buffer only the remainder. The existing path still handles a pending partial block. Instruction counts (cachegrind, same toolchain and base): dd_copy_separate_blocks 55,916,646 -> 5,447,251 -90.26% dd_copy_default 36,615,676 -> 30,718,154 -16.11% dd_copy_with_seek 8,511,906 -> 8,488,175 -0.28% dd_copy_with_skip 8,412,120 -> 8,389,437 -0.27% dd_copy_4k_blocks 4,742,612 -> 4,730,524 -0.25% dd_copy_8k_blocks 4,408,987 -> 4,401,037 -0.18% dd_copy_partial 3,222,128 -> 3,220,704 -0.04% dd_copy_64k_blocks 5,107,029 -> 5,105,557 -0.03% dd_copy_1m_blocks 9,410,822 -> 9,410,932 +0.00% Peak heap is unchanged (DHAT, 236,549 -> 236,546 bytes). --- src/uu/dd/src/bufferedoutput.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs index 7f4f92b8528..742394c54aa 100644 --- a/src/uu/dd/src/bufferedoutput.rs +++ b/src/uu/dd/src/bufferedoutput.rs @@ -68,13 +68,34 @@ impl<'a> BufferedOutput<'a> { /// block. The returned [`WriteStat`] object will include the /// number of blocks written during execution of this function. pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { + let obs = self.inner.settings.obs; + + // Fast path: with no partial block pending, the complete blocks can + // go straight to the inner writer, instead of being copied into the + // internal buffer only to be written and cleared again. This is the + // common case: whenever the incoming length is a multiple of `obs` + // -- as it is for the copy loop's usual full-sized reads -- nothing + // is left pending for the next call. + if self.buf.is_empty() { + let complete = buf.len() - buf.len() % obs; + if complete == 0 { + // Not even one complete block: buffer the whole thing and + // write zero blocks. + self.buf.extend_from_slice(buf); + return Ok(WriteStat::default()); + } + let wstat = self.inner.write_blocks(&buf[..complete])?; + self.buf.extend_from_slice(&buf[complete..]); + return Ok(wstat); + } + // Split the incoming buffer into two parts: the bytes to write // and the bytes to buffer for next time. // // If `buf` does not include enough bytes to form a full block, // just buffer the whole thing and write zero blocks. let n = self.buf.len() + buf.len(); - let rem = n % self.inner.settings.obs; + let rem = n % obs; let i = buf.len().saturating_sub(rem); let (to_write, to_buffer) = buf.split_at(i); From 1180ce88342649354273aa65568c767c5e465849 Mon Sep 17 00:00:00 2001 From: relative23 <62724298+relative23@users.noreply.github.com> Date: Thu, 23 Jul 2026 04:00:06 +0200 Subject: [PATCH 2/2] dd: shorten buffered output fast-path comments --- src/uu/dd/src/bufferedoutput.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs index 67b0b074309..d4c37b589ba 100644 --- a/src/uu/dd/src/bufferedoutput.rs +++ b/src/uu/dd/src/bufferedoutput.rs @@ -70,17 +70,10 @@ impl<'a> BufferedOutput<'a> { pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { let obs = self.inner.settings.obs; - // Fast path: with no partial block pending, the complete blocks can - // go straight to the inner writer, instead of being copied into the - // internal buffer only to be written and cleared again. This is the - // common case: whenever the incoming length is a multiple of `obs` - // -- as it is for the copy loop's usual full-sized reads -- nothing - // is left pending for the next call. + // Avoid copying complete blocks through the internal buffer. if self.buf.is_empty() { let complete = buf.len() - buf.len() % obs; if complete == 0 { - // Not even one complete block: buffer the whole thing and - // write zero blocks. self.buf.extend_from_slice(buf); return Ok(WriteStat::default()); }