Skip to content

Commit b5ad68e

Browse files
authored
Merge pull request #8774 from sylvestre/perf-du-bench
du: add benchmark
2 parents 2dfad36 + c4d8b4b commit b5ad68e

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

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/du/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,12 @@ windows-sys = { workspace = true, features = [
4040
[[bin]]
4141
name = "du"
4242
path = "src/main.rs"
43+
44+
[dev-dependencies]
45+
divan = { workspace = true }
46+
tempfile = { workspace = true }
47+
uucore = { workspace = true, features = ["benchmark"] }
48+
49+
[[bench]]
50+
name = "du_bench"
51+
harness = false

src/uu/du/benches/du_bench.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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 tempfile::TempDir;
8+
use uu_du::uumain;
9+
use uucore::benchmark::{fs_tree, run_util_function};
10+
11+
/// Helper to run du with given arguments on a directory
12+
fn bench_du_with_args(bencher: Bencher, temp_dir: &TempDir, args: &[&str]) {
13+
let temp_path_str = temp_dir.path().to_str().unwrap();
14+
let mut full_args = args.to_vec();
15+
full_args.push(temp_path_str);
16+
17+
bencher.bench(|| {
18+
black_box(run_util_function(uumain, &full_args));
19+
});
20+
}
21+
22+
/// Benchmark default du on balanced tree
23+
#[divan::bench(args = [(5, 4, 10)])]
24+
fn du_balanced_tree(
25+
bencher: Bencher,
26+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
27+
) {
28+
let temp_dir = TempDir::new().unwrap();
29+
fs_tree::create_balanced_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir);
30+
bench_du_with_args(bencher, &temp_dir, &[]);
31+
}
32+
33+
/// Benchmark du -a (all files) on balanced tree
34+
#[divan::bench(args = [(4, 3, 10)])]
35+
fn du_all_balanced_tree(
36+
bencher: Bencher,
37+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
38+
) {
39+
let temp_dir = TempDir::new().unwrap();
40+
fs_tree::create_balanced_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir);
41+
bench_du_with_args(bencher, &temp_dir, &["-a"]);
42+
}
43+
44+
/// Benchmark du -h (human readable) on balanced tree
45+
#[divan::bench(args = [(5, 4, 10)])]
46+
fn du_human_balanced_tree(
47+
bencher: Bencher,
48+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
49+
) {
50+
let temp_dir = TempDir::new().unwrap();
51+
fs_tree::create_balanced_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir);
52+
bench_du_with_args(bencher, &temp_dir, &["-h"]);
53+
}
54+
55+
/// Benchmark du on wide directory structures (many files/dirs, shallow)
56+
#[divan::bench(args = [(5000, 500)])]
57+
fn du_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
58+
let temp_dir = TempDir::new().unwrap();
59+
fs_tree::create_wide_tree(temp_dir.path(), total_files, total_dirs);
60+
bench_du_with_args(bencher, &temp_dir, &[]);
61+
}
62+
63+
/// Benchmark du -a on wide directory structures
64+
#[divan::bench(args = [(5000, 500)])]
65+
fn du_all_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
66+
let temp_dir = TempDir::new().unwrap();
67+
fs_tree::create_wide_tree(temp_dir.path(), total_files, total_dirs);
68+
bench_du_with_args(bencher, &temp_dir, &["-a"]);
69+
}
70+
71+
/// Benchmark du on deep directory structures
72+
#[divan::bench(args = [(100, 3)])]
73+
fn du_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) {
74+
let temp_dir = TempDir::new().unwrap();
75+
fs_tree::create_deep_tree(temp_dir.path(), depth, files_per_level);
76+
bench_du_with_args(bencher, &temp_dir, &[]);
77+
}
78+
79+
/// Benchmark du -s (summarize) on balanced tree
80+
#[divan::bench(args = [(5, 4, 10)])]
81+
fn du_summarize_balanced_tree(
82+
bencher: Bencher,
83+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
84+
) {
85+
let temp_dir = TempDir::new().unwrap();
86+
fs_tree::create_balanced_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir);
87+
bench_du_with_args(bencher, &temp_dir, &["-s"]);
88+
}
89+
90+
/// Benchmark du with --max-depth
91+
#[divan::bench(args = [(6, 4, 10)])]
92+
fn du_max_depth_balanced_tree(
93+
bencher: Bencher,
94+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
95+
) {
96+
let temp_dir = TempDir::new().unwrap();
97+
fs_tree::create_balanced_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir);
98+
bench_du_with_args(bencher, &temp_dir, &["--max-depth=2"]);
99+
}
100+
101+
fn main() {
102+
divan::main();
103+
}

src/uucore/src/lib/features/benchmark.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,80 @@ pub mod text_data {
307307
.join("\n")
308308
}
309309
}
310+
311+
/// Filesystem tree generation utilities for benchmarking
312+
pub mod fs_tree {
313+
use std::fs::{self, File};
314+
use std::path::Path;
315+
316+
/// Create a balanced directory tree for benchmarking
317+
///
318+
/// Creates a tree with specified depth, number of directories per level, and files per directory.
319+
/// This creates a realistic filesystem structure for testing recursive operations.
320+
pub fn create_balanced_tree(
321+
base_dir: &Path,
322+
depth: usize,
323+
dirs_per_level: usize,
324+
files_per_dir: usize,
325+
) {
326+
if depth == 0 {
327+
return;
328+
}
329+
330+
// Create files in current directory
331+
for file_idx in 0..files_per_dir {
332+
let file_path = base_dir.join(format!("f{file_idx}"));
333+
File::create(&file_path).unwrap();
334+
}
335+
336+
// Create subdirectories and recurse
337+
for dir_idx in 0..dirs_per_level {
338+
let dir_path = base_dir.join(format!("d{dir_idx}"));
339+
fs::create_dir(&dir_path).unwrap();
340+
create_balanced_tree(&dir_path, depth - 1, dirs_per_level, files_per_dir);
341+
}
342+
}
343+
344+
/// Create a wide directory tree (many files/dirs at shallow depth)
345+
///
346+
/// This creates a flat structure with many files and directories at a shallow depth,
347+
/// useful for benchmarking operations that need to traverse many entries quickly.
348+
pub fn create_wide_tree(base_dir: &Path, total_files: usize, total_dirs: usize) {
349+
// Create many files in root
350+
for file_idx in 0..total_files {
351+
let file_path = base_dir.join(format!("f{file_idx}"));
352+
File::create(&file_path).unwrap();
353+
}
354+
355+
// Create many directories with few files each
356+
for dir_idx in 0..total_dirs {
357+
let dir_path = base_dir.join(format!("d{dir_idx}"));
358+
fs::create_dir(&dir_path).unwrap();
359+
for file_idx in 0..5 {
360+
File::create(dir_path.join(format!("f{file_idx}"))).unwrap();
361+
}
362+
}
363+
}
364+
365+
/// Create a deep directory tree (deep nesting)
366+
///
367+
/// This creates a linear chain of deeply nested directories, useful for testing
368+
/// recursion depth handling and stack usage.
369+
pub fn create_deep_tree(base_dir: &Path, depth: usize, files_per_level: usize) {
370+
let mut current_dir = base_dir.to_path_buf();
371+
372+
for level in 0..depth {
373+
// Create files at this level
374+
for file_idx in 0..files_per_level {
375+
File::create(current_dir.join(format!("f{file_idx}"))).unwrap();
376+
}
377+
378+
// Create next level directory
379+
if level < depth - 1 {
380+
let next_dir = current_dir.join("d");
381+
fs::create_dir(&next_dir).unwrap();
382+
current_dir = next_dir;
383+
}
384+
}
385+
}
386+
}

0 commit comments

Comments
 (0)