diff --git a/Cargo.lock b/Cargo.lock index 2c8ea27b4..e8a8bf1b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -643,7 +643,7 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "paredit-cli" -version = "1.6.1" +version = "1.6.2" dependencies = [ "assert_cmd", "blake3", @@ -742,7 +742,7 @@ dependencies = [ [[package]] name = "paredit-core-cli" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "cap-std", @@ -763,7 +763,7 @@ dependencies = [ [[package]] name = "paredit-core-config" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-syntax", "serde_json", @@ -772,7 +772,7 @@ dependencies = [ [[package]] name = "paredit-core-edit" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-semantics", "paredit-core-syntax", @@ -782,7 +782,7 @@ dependencies = [ [[package]] name = "paredit-core-jsonrpc" -version = "1.6.1" +version = "1.6.2" dependencies = [ "serde_json", "thiserror", @@ -790,7 +790,7 @@ dependencies = [ [[package]] name = "paredit-core-lint-engine" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-safety", "paredit-core-semantics", @@ -801,7 +801,7 @@ dependencies = [ [[package]] name = "paredit-core-safety" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "paredit-core-syntax", @@ -812,7 +812,7 @@ dependencies = [ [[package]] name = "paredit-core-semantics" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-syntax", "proptest", @@ -821,7 +821,7 @@ dependencies = [ [[package]] name = "paredit-core-syntax" -version = "1.6.1" +version = "1.6.2" dependencies = [ "proptest", "thiserror", @@ -830,7 +830,7 @@ dependencies = [ [[package]] name = "paredit-core-workspace" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "cap-std", @@ -843,7 +843,7 @@ dependencies = [ [[package]] name = "paredit-feature-binding" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -860,7 +860,7 @@ dependencies = [ [[package]] name = "paredit-feature-change-summary" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "paredit-feature-code-metrics" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -881,7 +881,7 @@ dependencies = [ [[package]] name = "paredit-feature-conditional-conversion" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -895,7 +895,7 @@ dependencies = [ [[package]] name = "paredit-feature-data-report" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -905,11 +905,11 @@ dependencies = [ [[package]] name = "paredit-feature-editor" -version = "1.6.1" +version = "1.6.2" [[package]] name = "paredit-feature-emacs-lisp" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-semantics", @@ -918,7 +918,7 @@ dependencies = [ [[package]] name = "paredit-feature-external-check" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -930,7 +930,7 @@ dependencies = [ [[package]] name = "paredit-feature-extract" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -944,7 +944,7 @@ dependencies = [ [[package]] name = "paredit-feature-form-transform" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -959,7 +959,7 @@ dependencies = [ [[package]] name = "paredit-feature-function-parameter" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -974,7 +974,7 @@ dependencies = [ [[package]] name = "paredit-feature-generate" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -986,7 +986,7 @@ dependencies = [ [[package]] name = "paredit-feature-inline" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1001,7 +1001,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-binding-analysis" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-semantics", @@ -1010,7 +1010,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-build-system" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1021,7 +1021,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-call-shape" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1032,7 +1032,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-carp-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-clojure-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1051,7 +1051,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-clojure-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1062,7 +1062,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-compile-time" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1073,7 +1073,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-concurrency" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1084,7 +1084,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-condition-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1092,7 +1092,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-condition-system" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1103,7 +1103,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-conditional" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1118,7 +1118,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-contract-annotation" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1129,7 +1129,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-control-flow" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1144,7 +1144,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-convention" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1152,7 +1152,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-custom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1161,7 +1161,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-data-structure" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-cli", "paredit-core-lint-engine", @@ -1172,7 +1172,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-destructive-sequence" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-documentation" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1188,7 +1188,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-elisp-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1196,7 +1196,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-elisp-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-fennel-janet-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1212,7 +1212,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-fennel-janet-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1220,7 +1220,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-form-shape" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-generic-dispatch" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1244,7 +1244,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-hy-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-hy-lfe-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1260,7 +1260,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-introspection" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-iteration-flow" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1282,7 +1282,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-lfe-carp-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1290,7 +1290,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-loop-facility" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1298,7 +1298,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-macro-authoring" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-cli", "paredit-core-lint-engine", @@ -1308,7 +1308,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-numeric" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1323,7 +1323,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-object-system" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1334,7 +1334,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-package-hygiene" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-pathname-io" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1353,7 +1353,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-performance" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1361,7 +1361,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-portability" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1369,7 +1369,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-racket-depth" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-cli", "paredit-core-lint-engine", @@ -1379,7 +1379,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-repl-debug" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1393,7 +1393,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-safety" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1401,7 +1401,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-scheme-idiom" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-sequence" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1430,7 +1430,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-string-char" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-testing" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1456,7 +1456,7 @@ dependencies = [ [[package]] name = "paredit-feature-lint-type-declaration" -version = "1.6.1" +version = "1.6.2" dependencies = [ "paredit-core-lint-engine", "paredit-core-syntax", @@ -1464,7 +1464,7 @@ dependencies = [ [[package]] name = "paredit-feature-lisp-analysis" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1475,7 +1475,7 @@ dependencies = [ [[package]] name = "paredit-feature-migrate" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1486,7 +1486,7 @@ dependencies = [ [[package]] name = "paredit-feature-package" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "paredit-feature-project-analysis" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1518,7 +1518,7 @@ dependencies = [ [[package]] name = "paredit-feature-project-inventory" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1528,7 +1528,7 @@ dependencies = [ [[package]] name = "paredit-feature-query" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1538,7 +1538,7 @@ dependencies = [ [[package]] name = "paredit-feature-refactor-workflow" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "cap-std", @@ -1560,7 +1560,7 @@ dependencies = [ [[package]] name = "paredit-feature-remove-unused" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1578,7 +1578,7 @@ dependencies = [ [[package]] name = "paredit-feature-rename" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "paredit-feature-schema-check" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1604,7 +1604,7 @@ dependencies = [ [[package]] name = "paredit-feature-selector" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "paredit-feature-semantic-report" -version = "1.6.1" +version = "1.6.2" dependencies = [ "clap", "paredit-core-cli", @@ -1625,7 +1625,7 @@ dependencies = [ [[package]] name = "paredit-feature-similarity" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "clap", @@ -1640,7 +1640,7 @@ dependencies = [ [[package]] name = "paredit-feature-structural-diff" -version = "1.6.1" +version = "1.6.2" dependencies = [ "blake3", "clap", diff --git a/Cargo.toml b/Cargo.toml index b2997fd26..3130a8198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "paredit-cli" -version = "1.6.1" +version = "1.6.2" edition = "2024" publish = false rust-version = "1.85" @@ -243,7 +243,7 @@ exclude = ["fuzz", "xtask"] default-members = [".", "packages/*/*"] [workspace.package] -version = "1.6.1" +version = "1.6.2" edition = "2024" rust-version = "1.85" license = "MIT" diff --git a/docs/src/getting-started.md b/docs/src/getting-started.md index d93e887c8..1e851abc5 100644 --- a/docs/src/getting-started.md +++ b/docs/src/getting-started.md @@ -66,9 +66,9 @@ The examples above follow the latest default branch. For CI, production automation, or a reproducible developer environment, pin a release tag: ```sh -nix run github:nerima-lisp/paredit-cli/v1.6.1 -- --help -nix profile install github:nerima-lisp/paredit-cli/v1.6.1 -cargo install --git https://github.com/nerima-lisp/paredit-cli --tag v1.6.1 --locked +nix run github:nerima-lisp/paredit-cli/v1.6.2 -- --help +nix profile install github:nerima-lisp/paredit-cli/v1.6.2 +cargo install --git https://github.com/nerima-lisp/paredit-cli --tag v1.6.2 --locked ``` A tag is the release artifact — the crate is not published to a package diff --git a/docs/src/reference/compatibility.md b/docs/src/reference/compatibility.md index a6c8d090b..69441d48c 100644 --- a/docs/src/reference/compatibility.md +++ b/docs/src/reference/compatibility.md @@ -93,9 +93,9 @@ The crate is not published to a package registry, so pin the tag — or a reviewed commit — in whichever channel you use: ```sh -nix run github:nerima-lisp/paredit-cli/v1.6.1 -- --help -nix profile install github:nerima-lisp/paredit-cli/v1.6.1 -cargo install --git https://github.com/nerima-lisp/paredit-cli --tag v1.6.1 --locked +nix run github:nerima-lisp/paredit-cli/v1.6.2 -- --help +nix profile install github:nerima-lisp/paredit-cli/v1.6.2 +cargo install --git https://github.com/nerima-lisp/paredit-cli --tag v1.6.2 --locked ``` Release notes must call out anything that affects automation: new or diff --git a/packages/feature/lint-fennel-janet-depth/src/cost_tests.rs b/packages/feature/lint-fennel-janet-depth/src/cost_tests.rs index 2e34be582..db4c99406 100644 --- a/packages/feature/lint-fennel-janet-depth/src/cost_tests.rs +++ b/packages/feature/lint-fennel-janet-depth/src/cost_tests.rs @@ -25,7 +25,35 @@ //! //! Absolute nanoseconds from this machine are worthless: the audit ran with a //! load average above 10 and several sibling agents building in parallel. The -//! assertions are all *ratios*, and the printed table is for the report. +//! printed table is for the report. +//! +//! # What runs unattended +//! +//! Two things, both with enough headroom that machine load cannot decide them: +//! +//! - the **invocation counts**, which the head index and the dialect scope fix +//! before any `check` body runs, so they are identical idle and loaded; +//! - the **gap to [`materializing::RULE`]**, which is four orders of magnitude +//! (measured 9,000x in release, 13,000x in dev) against a 10x floor. This is +//! the assertion that actually catches a guard moved above its domain check. +//! +//! The **per-call doubling ratio is a benchmark, not a test**, and is +//! `#[ignore]`d — see [`ignored_bench_per_call_cost_does_not_grow_with_the_file`]. +//! It was asserted at `< 1.8` and failed a downstream `nix` build at 2.04 on +//! untouched code. Two reasons it cannot be a gate: +//! +//! - the real rules cost 16-75 ns per call in a release build, so the ratio of +//! two such measurements is mostly scheduler noise; +//! - doubling the file doubles the tree, so the traversal's working set stops +//! fitting in cache. That is constant work per call taking more time per +//! call, and it puts the idle-machine ratio at ~1.27 in release before any +//! contention — most of the budget spent on an effect that is not a +//! complexity change at all. +//! +//! The property it was reaching for is not lost: a `check` that materializes +//! the document is what +//! [`every_rule_stays_far_cheaper_than_a_materializing_one`] measures directly, +//! against a control built to have that exact shape. use std::path::Path; use std::time::Duration; @@ -315,68 +343,86 @@ fn every_rule_stays_far_cheaper_than_a_materializing_one() { ); } -/// Doubling the file must not change what one `check` call costs. +/// Every rule is dispatched once per occurrence of one of its heads, and no +/// more — so doubling the file doubles the dispatches rather than squaring +/// them. /// -/// This is deliberately expressed as **ns per call across two file sizes** -/// rather than as a ratio of totals. Both say the same thing when the totals -/// are large, but the totals here are not: at 27-160 ns per call the real -/// rules are near the clock's resolution, so their totals swing with the -/// scheduler while their per-call cost does not. +/// **Counts, not wall clock.** The head index and the dialect scope decide +/// these before any `check` body runs, so a loaded machine reports exactly +/// what an idle one does. A rule invoked more often than its heads occur is +/// one re-walking the document per match, which is the dispatch-side shape of +/// the same defect [`every_rule_stays_far_cheaper_than_a_materializing_one`] +/// catches on the cost side. +#[test] +fn each_rule_is_dispatched_once_per_head_occurrence() { + // Heads per repetition of the two fixtures, in `COST_ENTRIES` order: + // + // fennel-bad-unpack 9 DENSE_FENNEL + // fennel-nested-associative-operator 5 DENSE_FENNEL + // fennel-redundant-do 7 `fn` `when` `let` `each` + // `for` `while` `lambda` + // janet-dead-branch-on-constant-condition 4 `when` `unless` `if` `if-not` + // janet-unreachable-match-clause 1 `match` + // reference-materializing 1 `when`, Fennel only + const PER_REPETITION: [u64; 6] = [9, 5, 7, 4, 1, 1]; + + let counts = |repetitions: usize| -> Vec { + measure_once(repetitions) + .into_iter() + .map(|item| item.invocations) + .collect() + }; + + for repetitions in [200, 400] { + let observed = counts(repetitions); + let expected: Vec = PER_REPETITION + .iter() + .map(|per| per * repetitions as u64) + .collect(); + assert_eq!( + observed, expected, + "invocations must equal head occurrences at {repetitions} \ + repetitions; a rule dispatched more often than its heads occur is \ + being handed nodes its head filter should have excluded" + ); + } +} + +/// The per-call doubling ratio, as a benchmark rather than a gate. +/// +/// cargo test -p paredit-feature-lint-fennel-janet-depth --release \ +/// --lib -- --ignored --nocapture ignored_bench_per_call /// -/// The property is exact. A rule whose `check` is O(depth) costs the same per -/// call whatever the file size, so its ratio sits at 1. A rule that -/// materializes the document inside `check` is O(file) per call, so doubling -/// the file doubles its per-call cost — which is precisely what separates -/// `reference-materializing` (measured at 2.2) from every rule in this -/// package (measured at 0.5 to 1.3). +/// A `check` that is O(depth) costs the same per call whatever the file size; +/// one that materializes the document is O(file) per call, so doubling the +/// file doubles it. That is the difference between `reference-materializing` +/// and every rule here, and it is worth reading whenever a rule changes. +/// +/// It is not worth failing a build on. See the module docs: at 16-75 ns per +/// call in release the ratio is dominated by scheduler noise and by cache +/// working-set growth, and asserting it at `< 1.8` broke a downstream `nix` +/// build at 2.04 on code nobody had touched. #[test] -fn a_rules_per_call_cost_does_not_grow_with_the_file() { +#[ignore = "a benchmark: wall-clock ratios are unstable under parallel load"] +fn ignored_bench_per_call_cost_does_not_grow_with_the_file() { let small = measure_both(200); let large = measure_both(400); - let ratio_of = |name: &str| -> f64 { - let find = |set: &[Measured]| -> f64 { - set.iter() - .find(|item| item.name == name) - .map(|item| item.per_call) - .expect("rule present") - }; - find(&large) / find(&small).max(1.0) + let find = |set: &[Measured], name: &str| -> f64 { + set.iter() + .find(|item| item.name == name) + .map(|item| item.per_call) + .expect("rule present") }; for entry in &COST_ENTRIES { let name = entry.meta().name().as_str(); - if name == "reference-materializing" { - continue; - } - let ratio = ratio_of(name); - assert!( - ratio < 1.8, - "{name} cost {ratio:.2}x as much per call on a file twice the \ - size; a `check` that is O(file) rather than O(depth) is the usual \ - cause, and calling is_unevaluated_* before the domain check is \ - how that happens" + let at_small = find(&small, name); + let at_large = find(&large, name); + eprintln!( + "{name}: {at_small:.0} ns/call @200 → {at_large:.0} ns/call @400, \ + ratio {:.2}", + at_large / at_small.max(1e-9) ); } - - // The control. Without it the test above passes just as happily when the - // measurement is broken and every ratio is 1. - // - // Stated as an absolute magnitude rather than as the reference rule's own - // doubling ratio. The ratio is theoretically 2 but was observed between - // 1.48 and 2.96 across runs on this machine: taking the best of five - // samples pulls the *small* file's number down hardest, which compresses - // it. Six orders of magnitude between the two shapes is not compressible - // by any amount of scheduler noise. - let reference = large - .iter() - .find(|item| item.name == "reference-materializing") - .expect("rule present"); - assert!( - reference.per_call > 100_000.0, - "the reference rule cost only {:.0} ns per call; it materializes the \ - whole document on every one of them, so either the fixture stopped \ - matching `when` or the measurement is not live", - reference.per_call - ); }