mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
ci: add CodSpeed benchmarks for updatedb and locate (#705)
Drive both utilities end-to-end through their real entry points. - benches/updatedb_bench.rs: walks a generated tree (~8.5k paths) and writes a LOCATE02 database, measuring the walk + front-coding + write (full build and a -regex/-prune variant). Empty prune options keep the run deterministic regardless of temp_dir location. - benches/locate_bench.rs: builds the database once via updatedb, then scans it in -c/count mode so database decoding and pattern matching dominate (no-match, substring, -b basename, -i ignore-case, -r regex). Unix-only, matching the locate module's cfg. The existing CodSpeed workflow picks these up via cargo codspeed build/run.
This commit is contained in:
Generated
+22
-3
@@ -303,7 +303,7 @@ dependencies = [
|
||||
"codspeed",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
@@ -355,7 +355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools",
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -516,7 +516,7 @@ dependencies = [
|
||||
"ctor",
|
||||
"faccess",
|
||||
"filetime",
|
||||
"itertools",
|
||||
"itertools 0.14.0",
|
||||
"nix 0.31.3",
|
||||
"onig",
|
||||
"regex",
|
||||
@@ -768,6 +768,15 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
@@ -1477,6 +1486,16 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
|
||||
@@ -40,6 +40,14 @@ harness = false
|
||||
name = "xargs_bench"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "updatedb_bench"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "locate_bench"
|
||||
harness = false
|
||||
|
||||
[[bin]]
|
||||
name = "find"
|
||||
path = "src/find/main.rs"
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2024 the uutils developers
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! End-to-end benchmarks for `locate`, run through the real `locate_main` entry
|
||||
//! point. A LOCATE02 database is built once (via `updatedb`) from a generated
|
||||
//! tree, then each case scans it in `-c`/count mode so the database decoding
|
||||
//! and pattern matching dominate rather than terminal output.
|
||||
//!
|
||||
//! `locate` is Unix-only (it relies on `OsStr`/`CStr` byte APIs), so the
|
||||
//! benchmark body is compiled only there.
|
||||
|
||||
#[cfg(unix)]
|
||||
mod bench {
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use criterion::{black_box, criterion_group, Criterion};
|
||||
use findutils::locate::locate_main;
|
||||
use findutils::updatedb::updatedb_main;
|
||||
|
||||
/// Run `locate` end-to-end. `args` are the arguments after the program name.
|
||||
fn run(args: &[&str]) {
|
||||
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
|
||||
argv.push("locate");
|
||||
argv.extend_from_slice(args);
|
||||
let _ = locate_main(&argv);
|
||||
}
|
||||
|
||||
fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!("uu_locate_bench_{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
populate(&root, depth, dirs, files, 0);
|
||||
root
|
||||
}
|
||||
|
||||
fn populate(dir: &Path, depth: u32, dirs: u32, files: u32, seed: u32) {
|
||||
for f in 0..files {
|
||||
let n = seed.wrapping_add(f);
|
||||
let name = match n % 4 {
|
||||
0 => format!("module_{n}.rs"),
|
||||
1 => format!("data_{n}.txt"),
|
||||
2 => format!("image_{n}.png"),
|
||||
_ => format!("file_{n}.log"),
|
||||
};
|
||||
std::fs::write(dir.join(name), b"x").unwrap();
|
||||
}
|
||||
|
||||
if depth == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for d in 0..dirs {
|
||||
let sub = dir.join(format!("dir_{d}"));
|
||||
std::fs::create_dir_all(&sub).unwrap();
|
||||
populate(
|
||||
&sub,
|
||||
depth - 1,
|
||||
dirs,
|
||||
files,
|
||||
seed.wrapping_add((d + 1) * 31),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bench_e2e(c: &mut Criterion) {
|
||||
// ~8.5k paths, then front-coded into a LOCATE02 database once.
|
||||
let root = build_tree(4, 4, 25);
|
||||
let dir = root.to_str().unwrap();
|
||||
let db = std::env::temp_dir().join(format!("uu_locate_bench_{}.db", std::process::id()));
|
||||
let db_path = db.to_str().unwrap();
|
||||
|
||||
let _ = updatedb_main(&[
|
||||
"updatedb",
|
||||
&format!("--localpaths={dir}"),
|
||||
&format!("--output={db_path}"),
|
||||
"--prunepaths=",
|
||||
"--prunefs=",
|
||||
]);
|
||||
|
||||
let mut group = c.benchmark_group("locate");
|
||||
|
||||
// Pattern matching nothing: forces a full decode + match of every entry.
|
||||
group.bench_function("count_no_match", |b| {
|
||||
b.iter(|| run(black_box(&["-d", db_path, "-c", "NONEXISTENT_TOKEN_XYZ"])));
|
||||
});
|
||||
// Substring matching many entries (count mode → no per-match output).
|
||||
group.bench_function("count_many", |b| {
|
||||
b.iter(|| run(black_box(&["-d", db_path, "-c", ".log"])));
|
||||
});
|
||||
// Basename matching (-b) restricts the comparison to the file name.
|
||||
group.bench_function("basename", |b| {
|
||||
b.iter(|| run(black_box(&["-d", db_path, "-c", "-b", "module"])));
|
||||
});
|
||||
// Case-insensitive matching (-i).
|
||||
group.bench_function("ignore_case", |b| {
|
||||
b.iter(|| run(black_box(&["-d", db_path, "-c", "-i", "MODULE"])));
|
||||
});
|
||||
// Regex matching (-r) over the whole path.
|
||||
group.bench_function("regex", |b| {
|
||||
b.iter(|| {
|
||||
run(black_box(&[
|
||||
"-d",
|
||||
db_path,
|
||||
"-c",
|
||||
"-r",
|
||||
r"module_[0-9]+\.rs",
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let _ = std::fs::remove_file(&db);
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_e2e);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
criterion::criterion_main!(bench::benches);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn main() {}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2024 the uutils developers
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
//! End-to-end benchmarks for `updatedb`, run through the real `updatedb_main`
|
||||
//! entry point. Each case walks a generated directory tree (via the internal
|
||||
//! `find`), encodes it into the LOCATE02 database format and writes the result
|
||||
//! to a temp file, so the walk + front-coding + write dominate the measurement.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use findutils::updatedb::updatedb_main;
|
||||
|
||||
/// Run `updatedb` end-to-end. `args` are the arguments after the program name.
|
||||
fn run(args: &[&str]) {
|
||||
let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1);
|
||||
argv.push("updatedb");
|
||||
argv.extend_from_slice(args);
|
||||
let _ = updatedb_main(&argv);
|
||||
}
|
||||
|
||||
/// Build a directory tree to index. `depth` levels, each holding `dirs`
|
||||
/// sub-directories and `files` files with varied names. Returns the root.
|
||||
fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf {
|
||||
let root = std::env::temp_dir().join(format!("uu_updatedb_bench_{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
populate(&root, depth, dirs, files, 0);
|
||||
root
|
||||
}
|
||||
|
||||
fn populate(dir: &Path, depth: u32, dirs: u32, files: u32, seed: u32) {
|
||||
for f in 0..files {
|
||||
let n = seed.wrapping_add(f);
|
||||
let name = match n % 4 {
|
||||
0 => format!("module_{n}.rs"),
|
||||
1 => format!("data_{n}.txt"),
|
||||
2 => format!("image_{n}.png"),
|
||||
_ => format!("file_{n}.log"),
|
||||
};
|
||||
// Tiny files: updatedb only records names, so content size is irrelevant.
|
||||
std::fs::write(dir.join(name), b"x").unwrap();
|
||||
}
|
||||
|
||||
if depth == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for d in 0..dirs {
|
||||
let sub = dir.join(format!("dir_{d}"));
|
||||
std::fs::create_dir_all(&sub).unwrap();
|
||||
populate(
|
||||
&sub,
|
||||
depth - 1,
|
||||
dirs,
|
||||
files,
|
||||
seed.wrapping_add((d + 1) * 31),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_e2e(c: &mut Criterion) {
|
||||
// depth 4, 4 dirs/level, 25 files/dir → ~8.5k indexed paths.
|
||||
let root = build_tree(4, 4, 25);
|
||||
let dir = root.to_str().unwrap();
|
||||
let db = std::env::temp_dir().join(format!("uu_updatedb_bench_{}.db", std::process::id()));
|
||||
let db_path = db.to_str().unwrap();
|
||||
|
||||
let localpaths = format!("--localpaths={dir}");
|
||||
let output = format!("--output={db_path}");
|
||||
|
||||
let mut group = c.benchmark_group("updatedb");
|
||||
|
||||
// Full build: walk, front-code and write the whole tree. Empty prune
|
||||
// options keep the run deterministic regardless of where temp_dir lives.
|
||||
group.bench_function("build_full", |b| {
|
||||
b.iter(|| {
|
||||
run(black_box(&[
|
||||
&localpaths,
|
||||
&output,
|
||||
"--prunepaths=",
|
||||
"--prunefs=",
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
// Build with a prune clause that drops every `dir_0` subtree, exercising the
|
||||
// -regex/-prune path updatedb hands to find.
|
||||
group.bench_function("build_pruned", |b| {
|
||||
b.iter(|| {
|
||||
run(black_box(&[
|
||||
&localpaths,
|
||||
&output,
|
||||
"--prunepaths=.*/dir_0",
|
||||
"--prunefs=",
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let _ = std::fs::remove_file(&db);
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_e2e);
|
||||
criterion_main!(benches);
|
||||
Reference in New Issue
Block a user