Skip to content

Commit 503f010

Browse files
committed
ls: add some benchmarks
1 parent ea5c815 commit 503f010

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-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/ls/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,14 @@ fluent = { workspace = true }
4747
name = "ls"
4848
path = "src/main.rs"
4949

50+
[dev-dependencies]
51+
divan = { workspace = true }
52+
tempfile = { workspace = true }
53+
uucore = { workspace = true, features = ["benchmark"] }
54+
55+
[[bench]]
56+
name = "ls_bench"
57+
harness = false
58+
5059
[features]
5160
feat_selinux = ["selinux", "uucore/selinux"]

src/uu/ls/benches/ls_bench.rs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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_ls::uumain;
12+
use uucore::benchmark::run_util_function;
13+
14+
/// Helper to run ls with given arguments on a directory
15+
fn bench_ls_with_args(bencher: Bencher, temp_dir: &TempDir, args: &[&str]) {
16+
let temp_path_str = temp_dir.path().to_str().unwrap();
17+
let mut full_args = vec!["-R"];
18+
full_args.extend_from_slice(args);
19+
full_args.push(temp_path_str);
20+
21+
bencher.bench(|| {
22+
black_box(run_util_function(uumain, &full_args));
23+
});
24+
}
25+
26+
/// Create a deterministic directory tree for benchmarking ls -R performance
27+
fn create_directory_tree(
28+
base_dir: &Path,
29+
depth: usize,
30+
dirs_per_level: usize,
31+
files_per_dir: usize,
32+
) -> std::io::Result<()> {
33+
if depth == 0 {
34+
return Ok(());
35+
}
36+
37+
// Create files in current directory
38+
for file_idx in 0..files_per_dir {
39+
let file_path = base_dir.join(format!("file_{file_idx:04}.txt"));
40+
let mut file = File::create(&file_path)?;
41+
writeln!(file, "This is file {file_idx} at depth {depth}")?;
42+
}
43+
44+
// Create subdirectories and recurse
45+
for dir_idx in 0..dirs_per_level {
46+
let dir_path = base_dir.join(format!("subdir_{dir_idx:04}"));
47+
fs::create_dir(&dir_path)?;
48+
create_directory_tree(&dir_path, depth - 1, dirs_per_level, files_per_dir)?;
49+
}
50+
51+
Ok(())
52+
}
53+
54+
/// Create a wide directory tree (many files/dirs at shallow depth)
55+
fn create_wide_tree(base_dir: &Path, total_files: usize, total_dirs: usize) -> std::io::Result<()> {
56+
// Create many files in root
57+
for file_idx in 0..total_files {
58+
let file_path = base_dir.join(format!("wide_file_{file_idx:06}.txt"));
59+
let mut file = File::create(&file_path)?;
60+
writeln!(file, "Wide tree file {file_idx}")?;
61+
}
62+
63+
// Create many directories with few files each
64+
let files_per_subdir = 5;
65+
for dir_idx in 0..total_dirs {
66+
let dir_path = base_dir.join(format!("wide_dir_{dir_idx:06}"));
67+
fs::create_dir(&dir_path)?;
68+
69+
for file_idx in 0..files_per_subdir {
70+
let file_path = dir_path.join(format!("file_{file_idx}.txt"));
71+
let mut file = File::create(&file_path)?;
72+
writeln!(file, "File {file_idx} in wide dir {dir_idx}")?;
73+
}
74+
}
75+
76+
Ok(())
77+
}
78+
79+
/// Create a deep directory tree (few files/dirs but deep nesting)
80+
fn create_deep_tree(base_dir: &Path, depth: usize, files_per_level: usize) -> std::io::Result<()> {
81+
let mut current_dir = base_dir.to_path_buf();
82+
83+
for level in 0..depth {
84+
// Create files at this level
85+
for file_idx in 0..files_per_level {
86+
let file_path = current_dir.join(format!("deep_file_{level}_{file_idx}.txt"));
87+
let mut file = File::create(&file_path)?;
88+
writeln!(file, "File {file_idx} at depth level {level}")?;
89+
}
90+
91+
// Create next level directory
92+
if level < depth - 1 {
93+
let next_dir = current_dir.join(format!("level_{:04}", level + 1));
94+
fs::create_dir(&next_dir)?;
95+
current_dir = next_dir;
96+
}
97+
}
98+
99+
Ok(())
100+
}
101+
102+
/// Create a tree with mixed file types and permissions for comprehensive testing
103+
fn create_mixed_tree(base_dir: &Path) -> std::io::Result<()> {
104+
let extensions = ["txt", "log", "dat", "tmp", "bak", "cfg"];
105+
let sizes = [0, 100, 1024, 10240];
106+
107+
for (i, ext) in extensions.iter().enumerate() {
108+
for (j, &size) in sizes.iter().enumerate() {
109+
let file_path = base_dir.join(format!("mixed_file_{i}_{j}.{ext}"));
110+
let mut file = File::create(&file_path)?;
111+
112+
if size > 0 {
113+
let content = "x".repeat(size);
114+
file.write_all(content.as_bytes())?;
115+
}
116+
117+
// Set permissions only on Unix platforms
118+
#[cfg(unix)]
119+
{
120+
use std::os::unix::fs::PermissionsExt;
121+
let perms = fs::Permissions::from_mode(match (i + j) % 4 {
122+
0 => 0o644,
123+
1 => 0o755,
124+
2 => 0o600,
125+
_ => 0o444,
126+
});
127+
fs::set_permissions(&file_path, perms)?;
128+
}
129+
}
130+
}
131+
132+
// Create some subdirectories
133+
for i in 0..5 {
134+
let dir_path = base_dir.join(format!("mixed_subdir_{i}"));
135+
fs::create_dir(&dir_path)?;
136+
137+
for j in 0..3 {
138+
let file_path = dir_path.join(format!("sub_file_{j}.txt"));
139+
let mut file = File::create(&file_path)?;
140+
writeln!(file, "File {j} in subdir {i}")?;
141+
}
142+
}
143+
144+
Ok(())
145+
}
146+
147+
/// Benchmark ls -R on balanced directory tree
148+
#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])]
149+
fn ls_recursive_balanced_tree(
150+
bencher: Bencher,
151+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
152+
) {
153+
let temp_dir = TempDir::new().unwrap();
154+
create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap();
155+
bench_ls_with_args(bencher, &temp_dir, &[]);
156+
}
157+
158+
/// Benchmark ls -R -a -l on balanced directory tree (tests PR #8728 optimization)
159+
#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])]
160+
fn ls_recursive_long_all_balanced_tree(
161+
bencher: Bencher,
162+
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
163+
) {
164+
let temp_dir = TempDir::new().unwrap();
165+
create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap();
166+
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
167+
}
168+
169+
/// Benchmark ls -R on wide directory structures
170+
#[divan::bench(args = [(1000, 200), (5000, 500), (10000, 1000)])]
171+
fn ls_recursive_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
172+
let temp_dir = TempDir::new().unwrap();
173+
create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap();
174+
bench_ls_with_args(bencher, &temp_dir, &[]);
175+
}
176+
177+
/// Benchmark ls -R -a -l on wide directory structures
178+
#[divan::bench(args = [(1000, 200), (5000, 500)])]
179+
fn ls_recursive_long_all_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
180+
let temp_dir = TempDir::new().unwrap();
181+
create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap();
182+
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
183+
}
184+
185+
/// Benchmark ls -R on deep directory structures
186+
#[divan::bench(args = [(20, 3), (50, 2), (100, 1)])]
187+
fn ls_recursive_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) {
188+
let temp_dir = TempDir::new().unwrap();
189+
create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap();
190+
bench_ls_with_args(bencher, &temp_dir, &[]);
191+
}
192+
193+
/// Benchmark ls -R -a -l on deep directory structures
194+
#[divan::bench(args = [(20, 3), (50, 2)])]
195+
fn ls_recursive_long_all_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) {
196+
let temp_dir = TempDir::new().unwrap();
197+
create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap();
198+
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
199+
}
200+
201+
/// Benchmark ls -R on mixed file types (comprehensive real-world test)
202+
#[divan::bench]
203+
fn ls_recursive_mixed_tree(bencher: Bencher) {
204+
let temp_dir = TempDir::new().unwrap();
205+
create_mixed_tree(temp_dir.path()).unwrap();
206+
207+
for i in 0..10 {
208+
let subdir = temp_dir.path().join(format!("mixed_branch_{i}"));
209+
fs::create_dir(&subdir).unwrap();
210+
create_mixed_tree(&subdir).unwrap();
211+
}
212+
213+
bench_ls_with_args(bencher, &temp_dir, &[]);
214+
}
215+
216+
/// Benchmark ls -R -a -l on mixed file types (most comprehensive test)
217+
#[divan::bench]
218+
fn ls_recursive_long_all_mixed_tree(bencher: Bencher) {
219+
let temp_dir = TempDir::new().unwrap();
220+
create_mixed_tree(temp_dir.path()).unwrap();
221+
222+
for i in 0..10 {
223+
let subdir = temp_dir.path().join(format!("mixed_branch_{i}"));
224+
fs::create_dir(&subdir).unwrap();
225+
create_mixed_tree(&subdir).unwrap();
226+
}
227+
228+
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
229+
}
230+
231+
fn main() {
232+
divan::main();
233+
}

0 commit comments

Comments
 (0)