Skip to content

dd: write complete blocks without buffering them first#13471

Open
relative23 wants to merge 1 commit into
uutils:mainfrom
relative23:dd-buffered-output-fast-path
Open

dd: write complete blocks without buffering them first#13471
relative23 wants to merge 1 commit into
uutils:mainfrom
relative23:dd-buffered-output-fast-path

Conversation

@relative23

Copy link
Copy Markdown

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 iteration paid for a full memcpy of the block.

Change

With an empty internal buffer, write the complete blocks straight from the caller's slice and buffer only the remainder. The existing path is untouched and still handles a pending partial block. Pure optimization — no behavioural change.

Measurements

Instruction counts via cachegrind on the CodSpeed simulation binaries (cargo codspeed build -m simulation -p uu_dd), same toolchain, both built from this PR's base:

Benchmark main this PR Δ
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%

The two large wins are the configurations that actually use the buffered output path: dd_copy_separate_blocks (ibs=8K obs=16K) and dd_copy_default (no bs, so output is buffered). The measurement is deterministic — repeated runs of the same binary vary by ~2 instructions out of 36M.

Peak heap is unchanged: 236,549 → 236,546 bytes (DHAT, ibs=8K obs=16K, both binaries built in the same target directory).

Testing

  • cargo test -p uu_dd and the dd integration suite pass.
  • Byte- and statistics-equivalence against main over 17 configurations (odd ibs/obs, ibs>obs and obs>ibs, bs, conv=sync/block/unblock/swab, count, count_bytes, skip/seek, obs=1M, ibs=1M obs=7): output and the records in/records out lines are identical in every one.
  • cargo fmt, the repository clippy invocation (util/run-clippy.py --features all --workspace) and cspell are clean.

Note

This touches the same function as #13459, which fixes a correctness bug in the pending-partial-block path. The two are independent — this PR only adds the empty-buffer fast path and leaves the existing branch alone — but they overlap textually, so whichever lands second needs a trivial rebase.

@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by ×7.4

⚡ 2 improved benchmarks
✅ 337 untouched benchmarks
⏩ 46 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation dd_copy_separate_blocks 44,065.2 µs 967.6 µs ×46
Simulation dd_copy_default 13.5 ms 11.1 ms +21.48%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing relative23:dd-buffered-output-fast-path (3ff85c0) with main (7807c8d)

Open in CodSpeed

Footnotes

  1. 46 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

GNU testsuite comparison:

Skip an intermittent issue tests/cut/bounded-memory (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/pr/bounded-memory (fails in this run but passes in the 'main' branch)
Skip an intermittent issue tests/tail/tail-n0f (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/date/resolution (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/retry (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/cp/sparse-to-pipe is no longer failing!

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).
@relative23
relative23 force-pushed the dd-buffered-output-fast-path branch from e895791 to 3ff85c0 Compare July 21, 2026 09:05
@sylvestre

Copy link
Copy Markdown
Contributor

Wahou, impressive wins! Bravo

@Alonely0 Alonely0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants