Merge pull request #8738 from sylvestre/perf-ls
Some checks are pending
CICD / Style/deps (push) Waiting to run
CICD / Style/cargo-deny (push) Waiting to run
CICD / Test all features separately (push) Blocked by required conditions
CICD / Documentation/warnings (push) Waiting to run
CICD / MinRustV (push) Waiting to run
CICD / Dependencies (push) Waiting to run
CICD / Build/Makefile (push) Blocked by required conditions
CICD / Build/stable (push) Blocked by required conditions
CICD / Build/nightly (push) Blocked by required conditions
CICD / Binary sizes (push) Blocked by required conditions
CICD / Build (push) Blocked by required conditions
CICD / Tests/BusyBox test suite (push) Blocked by required conditions
CICD / Tests/Toybox test suite (push) Blocked by required conditions
CICD / Code Coverage (push) Waiting to run
CICD / Separate Builds (push) Waiting to run
CICD / Build/SELinux (push) Blocked by required conditions
CICD / Run benchmarks (CodSpeed) (push) Blocked by required conditions
GnuTests / Run GNU tests (native) (push) Waiting to run
GnuTests / Run GNU tests (SELinux) (push) Waiting to run
GnuTests / Aggregate GNU test results (push) Blocked by required conditions
Android / Test builds (push) Waiting to run
Code Quality / Style/format (push) Waiting to run
Code Quality / Style/lint (push) Waiting to run
Code Quality / Style/spelling (push) Waiting to run
Code Quality / Style/toml (push) Waiting to run
Code Quality / Style/Python (push) Waiting to run
Code Quality / Pre-commit hooks (push) Waiting to run
Devcontainer / Verify devcontainer (push) Waiting to run
FreeBSD / Style and Lint (push) Waiting to run
FreeBSD / Tests (push) Waiting to run
WSL2 / Test (push) Waiting to run

ls: add some benchmarks
This commit is contained in:
Sylvestre Ledru 2025-09-25 22:41:09 +02:00 committed by GitHub
commit f1c8a72a69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 252 additions and 1 deletions

View file

@ -137,7 +137,14 @@ jobs:
extra="${extra} --workspace"
;;
esac
S=$(cargo clippy --all-targets $extra --tests -pcoreutils -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
# * determine sub-crate utility list (similar to FreeBSD workflow)
if [[ "${{ matrix.job.features }}" == "all" ]]; then
UTILITY_LIST="$(./util/show-utils.sh --all-features)"
else
UTILITY_LIST="$(./util/show-utils.sh --features ${{ matrix.job.features }})"
fi
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)"
S=$(cargo clippy --all-targets $extra --tests --benches -pcoreutils ${CARGO_UTILITY_LIST_OPTIONS} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
style_spellcheck:

2
Cargo.lock generated
View file

@ -3606,11 +3606,13 @@ version = "0.2.2"
dependencies = [
"ansi-width",
"clap",
"codspeed-divan-compat",
"fluent",
"glob",
"hostname",
"lscolors",
"selinux",
"tempfile",
"terminal_size",
"thiserror 2.0.16",
"uucore",

View file

@ -47,5 +47,14 @@ fluent = { workspace = true }
name = "ls"
path = "src/main.rs"
[dev-dependencies]
divan = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["benchmark"] }
[[bench]]
name = "ls_bench"
harness = false
[features]
feat_selinux = ["selinux", "uucore/selinux"]

View file

@ -0,0 +1,233 @@
// 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_ls::uumain;
use uucore::benchmark::run_util_function;
/// Helper to run ls with given arguments on a directory
fn bench_ls_with_args(bencher: Bencher, temp_dir: &TempDir, args: &[&str]) {
let temp_path_str = temp_dir.path().to_str().unwrap();
let mut full_args = vec!["-R"];
full_args.extend_from_slice(args);
full_args.push(temp_path_str);
bencher.bench(|| {
black_box(run_util_function(uumain, &full_args));
});
}
/// Create a deterministic directory tree for benchmarking ls -R performance
fn create_directory_tree(
base_dir: &Path,
depth: usize,
dirs_per_level: usize,
files_per_dir: usize,
) -> std::io::Result<()> {
if depth == 0 {
return Ok(());
}
// Create files in current directory
for file_idx in 0..files_per_dir {
let file_path = base_dir.join(format!("file_{file_idx:04}.txt"));
let mut file = File::create(&file_path)?;
writeln!(file, "This is file {file_idx} at depth {depth}")?;
}
// Create subdirectories and recurse
for dir_idx in 0..dirs_per_level {
let dir_path = base_dir.join(format!("subdir_{dir_idx:04}"));
fs::create_dir(&dir_path)?;
create_directory_tree(&dir_path, depth - 1, dirs_per_level, files_per_dir)?;
}
Ok(())
}
/// Create a wide directory tree (many files/dirs at shallow depth)
fn create_wide_tree(base_dir: &Path, total_files: usize, total_dirs: usize) -> std::io::Result<()> {
// Create many files in root
for file_idx in 0..total_files {
let file_path = base_dir.join(format!("wide_file_{file_idx:06}.txt"));
let mut file = File::create(&file_path)?;
writeln!(file, "Wide tree file {file_idx}")?;
}
// Create many directories with few files each
let files_per_subdir = 5;
for dir_idx in 0..total_dirs {
let dir_path = base_dir.join(format!("wide_dir_{dir_idx:06}"));
fs::create_dir(&dir_path)?;
for file_idx in 0..files_per_subdir {
let file_path = dir_path.join(format!("file_{file_idx}.txt"));
let mut file = File::create(&file_path)?;
writeln!(file, "File {file_idx} in wide dir {dir_idx}")?;
}
}
Ok(())
}
/// Create a deep directory tree (few files/dirs but deep nesting)
fn create_deep_tree(base_dir: &Path, depth: usize, files_per_level: usize) -> std::io::Result<()> {
let mut current_dir = base_dir.to_path_buf();
for level in 0..depth {
// Create files at this level
for file_idx in 0..files_per_level {
let file_path = current_dir.join(format!("deep_file_{level}_{file_idx}.txt"));
let mut file = File::create(&file_path)?;
writeln!(file, "File {file_idx} at depth level {level}")?;
}
// Create next level directory
if level < depth - 1 {
let next_dir = current_dir.join(format!("level_{:04}", level + 1));
fs::create_dir(&next_dir)?;
current_dir = next_dir;
}
}
Ok(())
}
/// Create a tree with mixed file types and permissions for comprehensive testing
fn create_mixed_tree(base_dir: &Path) -> std::io::Result<()> {
let extensions = ["txt", "log", "dat", "tmp", "bak", "cfg"];
let sizes = [0, 100, 1024, 10240];
for (i, ext) in extensions.iter().enumerate() {
for (j, &size) in sizes.iter().enumerate() {
let file_path = base_dir.join(format!("mixed_file_{i}_{j}.{ext}"));
let mut file = File::create(&file_path)?;
if size > 0 {
let content = "x".repeat(size);
file.write_all(content.as_bytes())?;
}
// Set permissions only on Unix platforms
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let perms = fs::Permissions::from_mode(match (i + j) % 4 {
0 => 0o644,
1 => 0o755,
2 => 0o600,
_ => 0o444,
});
fs::set_permissions(&file_path, perms)?;
}
}
}
// Create some subdirectories
for i in 0..5 {
let dir_path = base_dir.join(format!("mixed_subdir_{i}"));
fs::create_dir(&dir_path)?;
for j in 0..3 {
let file_path = dir_path.join(format!("sub_file_{j}.txt"));
let mut file = File::create(&file_path)?;
writeln!(file, "File {j} in subdir {i}")?;
}
}
Ok(())
}
/// Benchmark ls -R on balanced directory tree
#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])]
fn ls_recursive_balanced_tree(
bencher: Bencher,
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
) {
let temp_dir = TempDir::new().unwrap();
create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap();
bench_ls_with_args(bencher, &temp_dir, &[]);
}
/// Benchmark ls -R -a -l on balanced directory tree (tests PR #8728 optimization)
#[divan::bench(args = [(3, 4, 8), (4, 3, 6), (5, 2, 10)])]
fn ls_recursive_long_all_balanced_tree(
bencher: Bencher,
(depth, dirs_per_level, files_per_dir): (usize, usize, usize),
) {
let temp_dir = TempDir::new().unwrap();
create_directory_tree(temp_dir.path(), depth, dirs_per_level, files_per_dir).unwrap();
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
}
/// Benchmark ls -R on wide directory structures
#[divan::bench(args = [(1000, 200), (5000, 500), (10000, 1000)])]
fn ls_recursive_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
let temp_dir = TempDir::new().unwrap();
create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap();
bench_ls_with_args(bencher, &temp_dir, &[]);
}
/// Benchmark ls -R -a -l on wide directory structures
#[divan::bench(args = [(1000, 200), (5000, 500)])]
fn ls_recursive_long_all_wide_tree(bencher: Bencher, (total_files, total_dirs): (usize, usize)) {
let temp_dir = TempDir::new().unwrap();
create_wide_tree(temp_dir.path(), total_files, total_dirs).unwrap();
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
}
/// Benchmark ls -R on deep directory structures
#[divan::bench(args = [(20, 3), (50, 2), (100, 1)])]
fn ls_recursive_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) {
let temp_dir = TempDir::new().unwrap();
create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap();
bench_ls_with_args(bencher, &temp_dir, &[]);
}
/// Benchmark ls -R -a -l on deep directory structures
#[divan::bench(args = [(20, 3), (50, 2)])]
fn ls_recursive_long_all_deep_tree(bencher: Bencher, (depth, files_per_level): (usize, usize)) {
let temp_dir = TempDir::new().unwrap();
create_deep_tree(temp_dir.path(), depth, files_per_level).unwrap();
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
}
/// Benchmark ls -R on mixed file types (comprehensive real-world test)
#[divan::bench]
fn ls_recursive_mixed_tree(bencher: Bencher) {
let temp_dir = TempDir::new().unwrap();
create_mixed_tree(temp_dir.path()).unwrap();
for i in 0..10 {
let subdir = temp_dir.path().join(format!("mixed_branch_{i}"));
fs::create_dir(&subdir).unwrap();
create_mixed_tree(&subdir).unwrap();
}
bench_ls_with_args(bencher, &temp_dir, &[]);
}
/// Benchmark ls -R -a -l on mixed file types (most comprehensive test)
#[divan::bench]
fn ls_recursive_long_all_mixed_tree(bencher: Bencher) {
let temp_dir = TempDir::new().unwrap();
create_mixed_tree(temp_dir.path()).unwrap();
for i in 0..10 {
let subdir = temp_dir.path().join(format!("mixed_branch_{i}"));
fs::create_dir(&subdir).unwrap();
create_mixed_tree(&subdir).unwrap();
}
bench_ls_with_args(bencher, &temp_dir, &["-a", "-l"]);
}
fn main() {
divan::main();
}