Skip to content

Commit e895791

Browse files
committed
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).
1 parent 50b8ead commit e895791

1 file changed

Lines changed: 22 additions & 1 deletion

File tree

src/uu/dd/src/bufferedoutput.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,34 @@ impl<'a> BufferedOutput<'a> {
6868
/// block. The returned [`WriteStat`] object will include the
6969
/// number of blocks written during execution of this function.
7070
pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result<WriteStat> {
71+
let obs = self.inner.settings.obs;
72+
73+
// Fast path: with no partial block pending, the complete blocks can
74+
// go straight to the inner writer, instead of being copied into the
75+
// internal buffer only to be written and cleared again. This is the
76+
// common case: whenever the incoming length is a multiple of `obs`
77+
// -- as it is for the copy loop's usual full-sized reads -- nothing
78+
// is left pending for the next call.
79+
if self.buf.is_empty() {
80+
let complete = buf.len() - buf.len() % obs;
81+
if complete == 0 {
82+
// Not even one complete block: buffer the whole thing and
83+
// write zero blocks.
84+
self.buf.extend_from_slice(buf);
85+
return Ok(WriteStat::default());
86+
}
87+
let wstat = self.inner.write_blocks(&buf[..complete])?;
88+
self.buf.extend_from_slice(&buf[complete..]);
89+
return Ok(wstat);
90+
}
91+
7192
// Split the incoming buffer into two parts: the bytes to write
7293
// and the bytes to buffer for next time.
7394
//
7495
// If `buf` does not include enough bytes to form a full block,
7596
// just buffer the whole thing and write zero blocks.
7697
let n = self.buf.len() + buf.len();
77-
let rem = n % self.inner.settings.obs;
98+
let rem = n % obs;
7899
let i = buf.len().saturating_sub(rem);
79100
let (to_write, to_buffer) = buf.split_at(i);
80101

0 commit comments

Comments
 (0)