Skip to content

Commit 77c3498

Browse files
naoNao89sylvestre
andauthored
feat(dd): add first benchmark suite for performance validation (#9136)
* 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 * 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. --------- Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
1 parent f43602d commit 77c3498

4 files changed

Lines changed: 278 additions & 0 deletions

File tree

.github/workflows/benchmarks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
- { package: uu_cksum }
2828
- { package: uu_cp }
2929
- { package: uu_cut }
30+
- { package: uu_dd }
3031
- { package: uu_du }
3132
- { package: uu_expand }
3233
- { package: uu_fold }

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/dd/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,12 @@ nix = { workspace = true, features = ["fs"] }
3737
[[bin]]
3838
name = "dd"
3939
path = "src/main.rs"
40+
41+
[dev-dependencies]
42+
divan = { workspace = true }
43+
tempfile = { workspace = true }
44+
uucore = { workspace = true, features = ["benchmark"] }
45+
46+
[[bench]]
47+
name = "dd_bench"
48+
harness = false

src/uu/dd/benches/dd_bench.rs

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// This file is part of the uutils coreutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use divan::{Bencher, black_box};
7+
use std::fs::{self, File};
8+
use std::io::Write;
9+
use std::path::Path;
10+
use tempfile::TempDir;
11+
use uu_dd::uumain;
12+
use uucore::benchmark::run_util_function;
13+
14+
fn create_test_file(path: &Path, size_mb: usize) {
15+
let buffer = vec![b'x'; size_mb * 1024 * 1024];
16+
let mut file = File::create(path).unwrap();
17+
file.write_all(&buffer).unwrap();
18+
file.sync_all().unwrap();
19+
}
20+
21+
fn remove_file(path: &Path) {
22+
if path.exists() {
23+
fs::remove_file(path).unwrap();
24+
}
25+
}
26+
27+
/// Benchmark basic dd copy with default settings
28+
#[divan::bench(args = [32])]
29+
fn dd_copy_default(bencher: Bencher, size_mb: usize) {
30+
let temp_dir = TempDir::new().unwrap();
31+
let input = temp_dir.path().join("input.bin");
32+
let output = temp_dir.path().join("output.bin");
33+
34+
create_test_file(&input, size_mb);
35+
36+
let input_str = input.to_str().unwrap();
37+
let output_str = output.to_str().unwrap();
38+
39+
bencher.bench(|| {
40+
remove_file(&output);
41+
black_box(run_util_function(
42+
uumain,
43+
&[
44+
&format!("if={input_str}"),
45+
&format!("of={output_str}"),
46+
"status=none",
47+
],
48+
));
49+
});
50+
}
51+
52+
/// Benchmark dd copy with 4KB block size (common page size)
53+
#[divan::bench(args = [24])]
54+
fn dd_copy_4k_blocks(bencher: Bencher, size_mb: usize) {
55+
let temp_dir = TempDir::new().unwrap();
56+
let input = temp_dir.path().join("input.bin");
57+
let output = temp_dir.path().join("output.bin");
58+
59+
create_test_file(&input, size_mb);
60+
61+
let input_str = input.to_str().unwrap();
62+
let output_str = output.to_str().unwrap();
63+
64+
bencher.bench(|| {
65+
remove_file(&output);
66+
black_box(run_util_function(
67+
uumain,
68+
&[
69+
&format!("if={input_str}"),
70+
&format!("of={output_str}"),
71+
"bs=4K",
72+
"status=none",
73+
],
74+
));
75+
});
76+
}
77+
78+
/// Benchmark dd copy with 64KB block size
79+
#[divan::bench(args = [64])]
80+
fn dd_copy_64k_blocks(bencher: Bencher, size_mb: usize) {
81+
let temp_dir = TempDir::new().unwrap();
82+
let input = temp_dir.path().join("input.bin");
83+
let output = temp_dir.path().join("output.bin");
84+
85+
create_test_file(&input, size_mb);
86+
87+
let input_str = input.to_str().unwrap();
88+
let output_str = output.to_str().unwrap();
89+
90+
bencher.bench(|| {
91+
remove_file(&output);
92+
black_box(run_util_function(
93+
uumain,
94+
&[
95+
&format!("if={input_str}"),
96+
&format!("of={output_str}"),
97+
"bs=64K",
98+
"status=none",
99+
],
100+
));
101+
});
102+
}
103+
104+
/// Benchmark dd copy with 1MB block size
105+
#[divan::bench(args = [128])]
106+
fn dd_copy_1m_blocks(bencher: Bencher, size_mb: usize) {
107+
let temp_dir = TempDir::new().unwrap();
108+
let input = temp_dir.path().join("input.bin");
109+
let output = temp_dir.path().join("output.bin");
110+
111+
create_test_file(&input, size_mb);
112+
113+
let input_str = input.to_str().unwrap();
114+
let output_str = output.to_str().unwrap();
115+
116+
bencher.bench(|| {
117+
remove_file(&output);
118+
black_box(run_util_function(
119+
uumain,
120+
&[
121+
&format!("if={input_str}"),
122+
&format!("of={output_str}"),
123+
"bs=1M",
124+
"status=none",
125+
],
126+
));
127+
});
128+
}
129+
130+
/// Benchmark dd copy with separate input and output block sizes
131+
#[divan::bench(args = [48])]
132+
fn dd_copy_separate_blocks(bencher: Bencher, size_mb: usize) {
133+
let temp_dir = TempDir::new().unwrap();
134+
let input = temp_dir.path().join("input.bin");
135+
let output = temp_dir.path().join("output.bin");
136+
137+
create_test_file(&input, size_mb);
138+
139+
let input_str = input.to_str().unwrap();
140+
let output_str = output.to_str().unwrap();
141+
142+
bencher.bench(|| {
143+
remove_file(&output);
144+
black_box(run_util_function(
145+
uumain,
146+
&[
147+
&format!("if={input_str}"),
148+
&format!("of={output_str}"),
149+
"ibs=8K",
150+
"obs=16K",
151+
"status=none",
152+
],
153+
));
154+
});
155+
}
156+
157+
/// Benchmark dd with count limit (partial copy)
158+
#[divan::bench(args = [32])]
159+
fn dd_copy_partial(bencher: Bencher, size_mb: usize) {
160+
let temp_dir = TempDir::new().unwrap();
161+
let input = temp_dir.path().join("input.bin");
162+
let output = temp_dir.path().join("output.bin");
163+
164+
create_test_file(&input, size_mb);
165+
166+
let input_str = input.to_str().unwrap();
167+
let output_str = output.to_str().unwrap();
168+
169+
bencher.bench(|| {
170+
remove_file(&output);
171+
black_box(run_util_function(
172+
uumain,
173+
&[
174+
&format!("if={input_str}"),
175+
&format!("of={output_str}"),
176+
"bs=4K",
177+
"count=1024",
178+
"status=none",
179+
],
180+
));
181+
});
182+
}
183+
184+
/// Benchmark dd with skip (seeking in input)
185+
#[divan::bench(args = [48])]
186+
fn dd_copy_with_skip(bencher: Bencher, size_mb: usize) {
187+
let temp_dir = TempDir::new().unwrap();
188+
let input = temp_dir.path().join("input.bin");
189+
let output = temp_dir.path().join("output.bin");
190+
191+
create_test_file(&input, size_mb);
192+
193+
let input_str = input.to_str().unwrap();
194+
let output_str = output.to_str().unwrap();
195+
196+
bencher.bench(|| {
197+
remove_file(&output);
198+
black_box(run_util_function(
199+
uumain,
200+
&[
201+
&format!("if={input_str}"),
202+
&format!("of={output_str}"),
203+
"bs=4K",
204+
"skip=256",
205+
"status=none",
206+
],
207+
));
208+
});
209+
}
210+
211+
/// Benchmark dd with seek (seeking in output)
212+
#[divan::bench(args = [48])]
213+
fn dd_copy_with_seek(bencher: Bencher, size_mb: usize) {
214+
let temp_dir = TempDir::new().unwrap();
215+
let input = temp_dir.path().join("input.bin");
216+
let output = temp_dir.path().join("output.bin");
217+
218+
create_test_file(&input, size_mb);
219+
220+
let input_str = input.to_str().unwrap();
221+
let output_str = output.to_str().unwrap();
222+
223+
bencher.bench(|| {
224+
remove_file(&output);
225+
black_box(run_util_function(
226+
uumain,
227+
&[
228+
&format!("if={input_str}"),
229+
&format!("of={output_str}"),
230+
"bs=4K",
231+
"seek=256",
232+
"status=none",
233+
],
234+
));
235+
});
236+
}
237+
238+
/// Benchmark dd with different block sizes for comparison
239+
#[divan::bench(args = [32])]
240+
fn dd_copy_8k_blocks(bencher: Bencher, size_mb: usize) {
241+
let temp_dir = TempDir::new().unwrap();
242+
let input = temp_dir.path().join("input.bin");
243+
let output = temp_dir.path().join("output.bin");
244+
245+
create_test_file(&input, size_mb);
246+
247+
let input_str = input.to_str().unwrap();
248+
let output_str = output.to_str().unwrap();
249+
250+
bencher.bench(|| {
251+
remove_file(&output);
252+
black_box(run_util_function(
253+
uumain,
254+
&[
255+
&format!("if={input_str}"),
256+
&format!("of={output_str}"),
257+
"bs=8K",
258+
"status=none",
259+
],
260+
));
261+
});
262+
}
263+
264+
fn main() {
265+
divan::main();
266+
}

0 commit comments

Comments
 (0)