diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 0000000..59aaa9f --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,37 @@ +name: CodSpeed + +on: + push: + branches: + - "main" + pull_request: + # `workflow_dispatch` allows CodSpeed to trigger backtest + # performance analysis in order to generate initial data. + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + codspeed: + name: Run benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust toolchain, cache and cargo-codspeed binary + uses: moonrepo/setup-rust@v0 + with: + channel: stable + cache-target: release + bins: cargo-codspeed + + - name: Build the benchmark target(s) + run: cargo codspeed build + + - name: Run the benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: cargo codspeed run diff --git a/Cargo.lock b/Cargo.lock index 5cbea54..97955ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -76,6 +82,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "argmax" version = "0.4.0" @@ -137,6 +152,12 @@ version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.60" @@ -183,6 +204,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.6.1" @@ -211,12 +259,80 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "codspeed" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57af92d1db7f6871b7e82c79cd87f2501db66f36b0eab924be6ea83dd6b2f3f3" +dependencies = [ + "anyhow", + "cc", + "colored", + "getrandom 0.2.17", + "glob", + "libc", + "nix 0.31.3", + "serde", + "serde_json", + "statrs", +] + +[[package]] +name = "codspeed-criterion-compat" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d31ae2e9ab23c29fa13bdfa06d012524176f5c0f4e25ec262cd829d947ebc5e" +dependencies = [ + "clap", + "codspeed", + "codspeed-criterion-compat-walltime", + "colored", + "regex", +] + +[[package]] +name = "codspeed-criterion-compat-walltime" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8605e40bab5114dcb0f76268e18880082b5798dec10757b5b58d2c3bbc7a1c" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "codspeed", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -232,6 +348,47 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "ctor" version = "1.0.7" @@ -355,6 +512,7 @@ dependencies = [ "assert_cmd", "chrono", "clap", + "codspeed-criterion-compat", "ctor", "faccess", "filetime", @@ -463,6 +621,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -483,6 +652,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -504,6 +684,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -565,6 +751,17 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -637,6 +834,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -761,6 +964,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "os_display" version = "0.1.4" @@ -782,6 +991,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -890,7 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom", + "getrandom 0.4.2", "rand_core", ] @@ -900,6 +1137,26 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.12.3" @@ -1100,6 +1357,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "statrs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" +dependencies = [ + "approx", + "num-traits", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1124,7 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -1365,6 +1632,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1462,6 +1735,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wild" version = "2.2.1" @@ -1561,13 +1844,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets", + "windows-targets 0.53.5", ] [[package]] @@ -1579,6 +1871,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows-targets" version = "0.53.5" @@ -1586,58 +1894,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "windows_x86_64_msvc" version = "0.53.1" @@ -1757,6 +2113,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index ee957bf..cf783c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ itertools = "0.14.0" thiserror = "2.0.12" [dev-dependencies] +criterion = { version = "4.7.0", package = "codspeed-criterion-compat" } assert_cmd = "2" ctor = "1.0" filetime = "0.2" @@ -31,6 +32,14 @@ rstest = "0.25.0" tempfile = "3" uutests = "0.9.0" +[[bench]] +name = "find_bench" +harness = false + +[[bench]] +name = "xargs_bench" +harness = false + [[bin]] name = "find" path = "src/find/main.rs" diff --git a/benches/find_bench.rs b/benches/find_bench.rs new file mode 100644 index 0000000..69082fa --- /dev/null +++ b/benches/find_bench.rs @@ -0,0 +1,166 @@ +// 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 `find`, run through the real `find_main` entry +//! point so they exercise argument parsing, the matcher tree and the directory +//! walk together. Output is sent to a sink so the benchmarks measure the work +//! `find` does rather than terminal I/O. + +use std::cell::RefCell; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use findutils::find::{find_main, Dependencies}; + +/// `Dependencies` implementation that throws output away. A fixed `now` keeps +/// time-based matchers (`-newer`, `-mtime`, …) deterministic across runs. +struct SinkDependencies { + output: RefCell, + now: SystemTime, +} + +impl SinkDependencies { + fn new() -> Self { + Self { + output: RefCell::new(io::sink()), + now: SystemTime::now(), + } + } +} + +impl Dependencies for SinkDependencies { + fn get_output(&self) -> &RefCell { + &self.output + } + + fn now(&self) -> SystemTime { + self.now + } + + fn confirm(&self, _prompt: &str) -> bool { + false + } +} + +/// Run `find` end-to-end. `args` are the arguments after the program name +/// (flags, paths, expression). The exit status is ignored — we only care about +/// the work performed. +fn run(args: &[&str]) { + let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1); + argv.push("find"); + argv.extend_from_slice(args); + let deps = SinkDependencies::new(); + let _ = find_main(&argv, &deps); +} + +/// Build a moderately deep directory tree to walk. `depth` levels, each holding +/// `dirs` sub-directories and `files` files. File names vary so name/regex +/// matchers have something to discriminate on, and sizes vary so `-size` does +/// real work. Returns the root directory. +fn build_tree(depth: u32, dirs: u32, files: u32) -> PathBuf { + let root = std::env::temp_dir().join(format!("uu_find_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); + // A mix of extensions and a sprinkling of a rare marker name. + let name = match n % 5 { + 0 => format!("module_{n}.rs"), + 1 => format!("data_{n}.txt"), + 2 => format!("image_{n}.png"), + 3 => format!("README_{n}.md"), + _ if n % 500 == 0 => format!("RAREHIT_{n}.log"), + _ => format!("file_{n}.log"), + }; + // Sizes from a few bytes up to ~8 KiB so -size buckets differ. + let size = (n as usize % 8192) + 1; + let path = dir.join(name); + std::fs::write(&path, vec![b'x'; size]).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 → a few thousand entries. + let root = build_tree(4, 4, 25); + let dir = root.to_str().unwrap(); + + let mut group = c.benchmark_group("find"); + + // Plain full walk with the implicit -print. + group.bench_function("walk_all", |b| { + b.iter(|| run(black_box(&[dir]))); + }); + // Filter by file type only. + group.bench_function("type_f", |b| { + b.iter(|| run(black_box(&[dir, "-type", "f"]))); + }); + // Glob name match — a common invocation. + group.bench_function("name_glob", |b| { + b.iter(|| run(black_box(&[dir, "-name", "*.rs"]))); + }); + // Case-insensitive name match. + group.bench_function("iname_glob", |b| { + b.iter(|| run(black_box(&[dir, "-iname", "*.RS"]))); + }); + // Regex over the whole path. + group.bench_function("regex_path", |b| { + b.iter(|| run(black_box(&[dir, "-regex", r".*/module_[0-9]+\.rs"]))); + }); + // Size predicate forces a stat per entry. + group.bench_function("size_gt", |b| { + b.iter(|| run(black_box(&[dir, "-type", "f", "-size", "+4k"]))); + }); + // Combined predicate with AND/OR and grouping. + group.bench_function("combined_expr", |b| { + b.iter(|| { + run(black_box(&[ + dir, "-type", "f", "(", "-name", "*.rs", "-o", "-name", "*.md", ")", + ])); + }); + }); + // Prune whole subtrees, then print the rest. + group.bench_function("prune", |b| { + b.iter(|| { + run(black_box(&[ + dir, "-name", "dir_0", "-prune", "-o", "-type", "f", "-print", + ])); + }); + }); + // -printf with several directives exercises the formatter. + group.bench_function("printf", |b| { + b.iter(|| run(black_box(&[dir, "-type", "f", "-printf", "%p %s %y\\n"]))); + }); + + group.finish(); + + let _ = std::fs::remove_dir_all(&root); +} + +criterion_group!(benches, bench_e2e); +criterion_main!(benches); diff --git a/benches/xargs_bench.rs b/benches/xargs_bench.rs new file mode 100644 index 0000000..2be2a05 --- /dev/null +++ b/benches/xargs_bench.rs @@ -0,0 +1,77 @@ +// 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 `xargs`, run through the real `xargs_main` entry +//! point. Input is supplied via `-a ` (rather than stdin) so the harness +//! can feed a fixed corpus without touching the process's stdin, and the +//! command is `true` so the measurement is dominated by `xargs`'s own work +//! (reading, splitting and batching arguments) rather than the child. + +use std::path::PathBuf; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use findutils::xargs::xargs_main; + +/// Run `xargs` end-to-end. `args` are the arguments after the program name. +/// The exit status is ignored — we only care about the work performed. +fn run(args: &[&str]) { + let mut argv: Vec<&str> = Vec::with_capacity(args.len() + 1); + argv.push("xargs"); + argv.extend_from_slice(args); + let _ = xargs_main(&argv); +} + +/// Write `count` short tokens to a temp file, joined by `sep`. Returns the path. +fn build_input(count: u32, sep: &str, tag: &str) -> PathBuf { + let mut content = String::new(); + for i in 0..count { + if i > 0 { + content.push_str(sep); + } + content.push_str(&format!("item_{i}")); + } + let path = std::env::temp_dir().join(format!("uu_xargs_bench_{}_{tag}", std::process::id())); + std::fs::write(&path, content).unwrap(); + path +} + +fn bench_e2e(c: &mut Criterion) { + // Whitespace- and newline-separated corpora, plus a NUL-separated one for + // the `-0` path. A few thousand short tokens keeps parsing dominant while + // `true` batches stay to a handful of (untraced) spawns. + let ws = build_input(4000, " ", "ws"); + let nul = build_input(4000, "\0", "nul"); + let ws_path = ws.to_str().unwrap(); + let nul_path = nul.to_str().unwrap(); + + let mut group = c.benchmark_group("xargs"); + + // Default whitespace splitting, single batch (fits one command line). + group.bench_function("split_whitespace", |b| { + b.iter(|| run(black_box(&["-a", ws_path, "true"]))); + }); + // NUL-delimited input (`find -print0 | xargs -0` shape). + group.bench_function("split_null", |b| { + b.iter(|| run(black_box(&["-0", "-a", nul_path, "true"]))); + }); + // Cap arguments per command with -n, exercising the batching path across + // several (untraced) invocations. + group.bench_function("batched_n", |b| { + b.iter(|| run(black_box(&["-a", ws_path, "-n", "1000", "true"]))); + }); + // Bound each command line by size with -s, another batching trigger. + group.bench_function("batched_size", |b| { + b.iter(|| run(black_box(&["-a", ws_path, "-s", "4096", "true"]))); + }); + + group.finish(); + + let _ = std::fs::remove_file(&ws); + let _ = std::fs::remove_file(&nul); +} + +criterion_group!(benches, bench_e2e); +criterion_main!(benches);