From a8082ad658f285b22c016dfa99e8ba1b46302706 Mon Sep 17 00:00:00 2001 From: naoNao89 <90588855+naoNao89@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:12:53 +0700 Subject: [PATCH 1/2] feat(dd): add comprehensive benchmark suite for O_DIRECT optimization - Create dd's first benchmark suite using divan framework - Benchmark various block sizes (4K, 8K, 64K, 1M) to measure performance - Test different dd scenarios: default, partial copy, skip, seek operations - Measure impact of separate input/output block sizes - All benchmarks use status=none to avoid output noise - Benchmarks verify the O_DIRECT buffer alignment optimization - Follows existing uutils benchmark patterns and conventions --- .github/workflows/benchmarks.yml | 1 + Cargo.lock | 2 + src/uu/dd/Cargo.toml | 9 ++ src/uu/dd/benches/dd_bench.rs | 266 +++++++++++++++++++++++++++++++ 4 files changed, 278 insertions(+) create mode 100644 src/uu/dd/benches/dd_bench.rs diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 7485555e0cb..2c1ea1e2825 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -27,6 +27,7 @@ jobs: - { package: uu_cksum } - { package: uu_cp } - { package: uu_cut } + - { package: uu_dd } - { package: uu_du } - { package: uu_expand } - { package: uu_fold } diff --git a/Cargo.lock b/Cargo.lock index 87471acd621..533e4e6130b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3336,11 +3336,13 @@ name = "uu_dd" version = "0.3.0" dependencies = [ "clap", + "codspeed-divan-compat", "fluent", "gcd", "libc", "nix", "signal-hook", + "tempfile", "thiserror 2.0.17", "uucore", ] diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 4633bc06b72..634da24edf3 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -37,3 +37,12 @@ nix = { workspace = true, features = ["fs"] } [[bin]] name = "dd" path = "src/main.rs" + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bench]] +name = "dd_bench" +harness = false diff --git a/src/uu/dd/benches/dd_bench.rs b/src/uu/dd/benches/dd_bench.rs new file mode 100644 index 00000000000..0629d3a995e --- /dev/null +++ b/src/uu/dd/benches/dd_bench.rs @@ -0,0 +1,266 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use divan::{Bencher, black_box}; +use std::fs::{self, File}; +use std::io::Write; +use std::path::Path; +use tempfile::TempDir; +use uu_dd::uumain; +use uucore::benchmark::run_util_function; + +fn create_test_file(path: &Path, size_mb: usize) { + let buffer = vec![b'x'; size_mb * 1024 * 1024]; + let mut file = File::create(path).unwrap(); + file.write_all(&buffer).unwrap(); + file.sync_all().unwrap(); +} + +fn remove_file(path: &Path) { + if path.exists() { + fs::remove_file(path).unwrap(); + } +} + +/// Benchmark basic dd copy with default settings +#[divan::bench(args = [16])] +fn dd_copy_default(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "status=none", + ], + )); + }); +} + +/// Benchmark dd copy with 4KB block size (common page size) +#[divan::bench(args = [16])] +fn dd_copy_4k_blocks(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=4K", + "status=none", + ], + )); + }); +} + +/// Benchmark dd copy with 64KB block size +#[divan::bench(args = [16])] +fn dd_copy_64k_blocks(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=64K", + "status=none", + ], + )); + }); +} + +/// Benchmark dd copy with 1MB block size +#[divan::bench(args = [16])] +fn dd_copy_1m_blocks(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=1M", + "status=none", + ], + )); + }); +} + +/// Benchmark dd copy with separate input and output block sizes +#[divan::bench(args = [16])] +fn dd_copy_separate_blocks(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "ibs=8K", + "obs=16K", + "status=none", + ], + )); + }); +} + +/// Benchmark dd with count limit (partial copy) +#[divan::bench(args = [16])] +fn dd_copy_partial(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=4K", + "count=1024", + "status=none", + ], + )); + }); +} + +/// Benchmark dd with skip (seeking in input) +#[divan::bench(args = [16])] +fn dd_copy_with_skip(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=4K", + "skip=256", + "status=none", + ], + )); + }); +} + +/// Benchmark dd with seek (seeking in output) +#[divan::bench(args = [16])] +fn dd_copy_with_seek(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=4K", + "seek=256", + "status=none", + ], + )); + }); +} + +/// Benchmark dd with different block sizes for comparison +#[divan::bench(args = [16])] +fn dd_copy_8k_blocks(bencher: Bencher, size_mb: usize) { + let temp_dir = TempDir::new().unwrap(); + let input = temp_dir.path().join("input.bin"); + let output = temp_dir.path().join("output.bin"); + + create_test_file(&input, size_mb); + + let input_str = input.to_str().unwrap(); + let output_str = output.to_str().unwrap(); + + bencher.bench(|| { + remove_file(&output); + black_box(run_util_function( + uumain, + &[ + &format!("if={input_str}"), + &format!("of={output_str}"), + "bs=8K", + "status=none", + ], + )); + }); +} + +fn main() { + divan::main(); +} From cbbd26523d8aed842bb401690aa1349d2d3c040d Mon Sep 17 00:00:00 2001 From: naoNao89 <90588855+naoNao89@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:02:29 +0700 Subject: [PATCH 2/2] bench(dd): increase dataset sizes for consistent timing Increase benchmark dataset sizes to achieve consistent 100-300ms timing: - dd_copy_default: 16 -> 32 MB - dd_copy_4k_blocks: 16 -> 24 MB - dd_copy_64k_blocks: 16 -> 64 MB - dd_copy_1m_blocks: 16 -> 128 MB - dd_copy_separate_blocks: 16 -> 48 MB - dd_copy_partial: 16 -> 32 MB - dd_copy_with_skip: 16 -> 48 MB - dd_copy_with_seek: 16 -> 48 MB - dd_copy_8k_blocks: 16 -> 32 MB This ensures stable, repeatable benchmark measurements across different systems. --- src/uu/dd/benches/dd_bench.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/uu/dd/benches/dd_bench.rs b/src/uu/dd/benches/dd_bench.rs index 0629d3a995e..0a86f5de18b 100644 --- a/src/uu/dd/benches/dd_bench.rs +++ b/src/uu/dd/benches/dd_bench.rs @@ -25,7 +25,7 @@ fn remove_file(path: &Path) { } /// Benchmark basic dd copy with default settings -#[divan::bench(args = [16])] +#[divan::bench(args = [32])] fn dd_copy_default(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -50,7 +50,7 @@ fn dd_copy_default(bencher: Bencher, size_mb: usize) { } /// Benchmark dd copy with 4KB block size (common page size) -#[divan::bench(args = [16])] +#[divan::bench(args = [24])] fn dd_copy_4k_blocks(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -76,7 +76,7 @@ fn dd_copy_4k_blocks(bencher: Bencher, size_mb: usize) { } /// Benchmark dd copy with 64KB block size -#[divan::bench(args = [16])] +#[divan::bench(args = [64])] fn dd_copy_64k_blocks(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -102,7 +102,7 @@ fn dd_copy_64k_blocks(bencher: Bencher, size_mb: usize) { } /// Benchmark dd copy with 1MB block size -#[divan::bench(args = [16])] +#[divan::bench(args = [128])] fn dd_copy_1m_blocks(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -128,7 +128,7 @@ fn dd_copy_1m_blocks(bencher: Bencher, size_mb: usize) { } /// Benchmark dd copy with separate input and output block sizes -#[divan::bench(args = [16])] +#[divan::bench(args = [48])] fn dd_copy_separate_blocks(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -155,7 +155,7 @@ fn dd_copy_separate_blocks(bencher: Bencher, size_mb: usize) { } /// Benchmark dd with count limit (partial copy) -#[divan::bench(args = [16])] +#[divan::bench(args = [32])] fn dd_copy_partial(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -182,7 +182,7 @@ fn dd_copy_partial(bencher: Bencher, size_mb: usize) { } /// Benchmark dd with skip (seeking in input) -#[divan::bench(args = [16])] +#[divan::bench(args = [48])] fn dd_copy_with_skip(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -209,7 +209,7 @@ fn dd_copy_with_skip(bencher: Bencher, size_mb: usize) { } /// Benchmark dd with seek (seeking in output) -#[divan::bench(args = [16])] +#[divan::bench(args = [48])] fn dd_copy_with_seek(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin"); @@ -236,7 +236,7 @@ fn dd_copy_with_seek(bencher: Bencher, size_mb: usize) { } /// Benchmark dd with different block sizes for comparison -#[divan::bench(args = [16])] +#[divan::bench(args = [32])] fn dd_copy_8k_blocks(bencher: Bencher, size_mb: usize) { let temp_dir = TempDir::new().unwrap(); let input = temp_dir.path().join("input.bin");