Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion XFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ See also: [Configuration](#topic-config), [Archives](#topic-archive), [Output](#
<a id="flag-jobs"></a>

- `-j N, -j=N, --jobs=N|all` - directory-read, eligible content-match, comparison and -exec workers _(global, find, xff, rg)_
`N` is a positive integer; use `-j 4`, `-j=4`, or `--jobs=4`. The conventional attached short form `-j4` is also accepted. Every form accepts `all` in place of `N`. `-j 1` makes directory reads and `-exec ... ;` synchronous. At larger values, xff reads directories using one command-owned, lazily started pool of up to `N-1` background workers plus the calling coordinator, reused for directory reads, required eager metadata, eligible content matching and file comparison. Comparison trees are coordinated sequentially within that same budget. The caller participates in eligible content and comparison work. Workers drain batches of independent work; small workloads stay on the coordinator. Lazy and metadata-free listings do not start stat workers. Independent content predicates and rg searches match in bounded parallel batches, with output kept in traversal order. Small initial batches, archive members, stateful expressions stay on the coordinator. Metadata-consuming output does not itself prevent eligible content filters from running in workers; reductions and rendering remain serial. Independent regular-file comparison pairs can also use bounded workers, preserving relative-path output order. Batches of only small or metadata-only comparisons stay inline; archive and owned filesystem sources stay serial. Xff may independently keep up to `N` semicolon-form `-exec` / `-execdir` children outstanding. Reads and children can overlap; `N` is not one shared operation budget. The children's truth value is therefore success on launch. The `... +` batch forms still run once after the walk and propagate a failing exit status. With no flag, xff uses one fewer than the detected cores, capped at 15 and floored at 1; find and rg modes use every detected core. `all` always means every detected core.
`N` is a positive integer; use `-j 4`, `-j=4`, or `--jobs=4`. The conventional attached short form `-j4` is also accepted. Every form accepts `all` in place of `N`. `-j 1` makes directory reads and `-exec ... ;` synchronous. At larger values, xff reads directories using one command-owned, lazily started pool of up to `N-1` background workers plus the calling coordinator, reused for directory reads, required eager metadata, eligible content matching and file comparison. Comparison trees are coordinated sequentially within that same budget. The caller participates in eligible content and comparison work. Directory read-ahead uses a bounded ordered window and a shared retained-storage allowance; oversized speculative listings are read on demand instead. Unneeded queued reads are canceled after pruning or stopping, but an in-flight filesystem call must finish. Workers reuse the same executor for independent work; small workloads stay on the coordinator. Lazy and metadata-free listings do not start stat workers. Independent content predicates and rg searches match in bounded parallel batches, with output kept in traversal order. Small initial batches, archive members, stateful expressions stay on the coordinator. Metadata-consuming output does not itself prevent eligible content filters from running in workers; reductions and rendering remain serial. Independent regular-file comparison pairs can also use bounded workers, preserving relative-path output order. Batches of only small or metadata-only comparisons stay inline; archive and owned filesystem sources stay serial. Xff may independently keep up to `N` semicolon-form `-exec` / `-execdir` children outstanding. Reads and children can overlap; `N` is not one shared operation budget. The children's truth value is therefore success on launch. The `... +` batch forms still run once after the walk and propagate a failing exit status. With no flag, xff uses one fewer than the detected cores, capped at 15 and floored at 1; find and rg modes use every detected core. `all` always means every detected core.
See also: [Output](#topic-output), [Statistics](#topic-stats)

<a id="flag-sort"></a>
Expand Down
42 changes: 39 additions & 3 deletions docs/benchmark-correctness-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,39 @@ The interrupted `/tmp/xff-perf-shared-executor-v2` run is diagnostic only:
correctness builds overlapped its measurements, and its measured source predates
the final scheduling changes. It supplies no acceptance timing evidence.

### Bounded directory-frontier experiment

The command-ownership follow-up is draft [PR #996](https://github.com/mboworks/xff/pull/996).
A separate candidate based on that branch replaces whole-sibling directory drain
jobs with an ordered read-ahead window. Its persistent command executor dispatches
one leaf directory read per speculative task and alternates speculative and
foreground queues when both contain work. This is nonpreemptive job-boundary
fairness, not a guarantee against long foreground drains or blocking VFS calls.

Nested and mounted-filesystem walks share at most twice the started background
worker count in unfinished reads and a provisional 8 MiB charged-storage allowance.
Pending requests charge bookkeeping/path storage before dispatch; completed
speculative listings add vector, string and status charges. Finished listings
release execution slots while keeping byte charges until consumed or destroyed.
This distinction permits deeper reads while older sibling listings remain cached.
Required listings, retained ancestor listings, in-flight VFS storage and allocator
overhead are excluded: the allowance is not a total-memory or RSS bound.

Per-request and frontier cancellation skip unneeded queued reads after pruning or
termination. Already-running VFS calls finish, and the walker still joins its jobs
before borrowed state is destroyed. Oversized completed speculative listings are
discarded and read on demand; a listing already required by the caller belongs to
ordinary traversal working memory and can be transferred without a duplicate read.

In-memory controls cover shared slot/byte admission, concurrent retention,
reservation ownership, queue-class fairness, pruning before filesystem access,
early-stop read counts, and nested progress while parent siblings remain cached.
The inherited directory/stat/content admission policies are deliberately unchanged
in this experiment. In particular, accumulated completed content work is still
not valid evidence of sufficient remaining work. The bookkeeping estimate, retained
byte policy and task granularity need allocation profiles and repeated native
measurements. No timing, total-memory improvement or macOS acceptance is claimed.

### Memory-accounting finding and remaining acceptance work

Direct `/usr/bin/time -v` runs show approximately 5.3-5.4 MiB peak RSS for a small
Expand All @@ -330,8 +363,11 @@ arena sizes. This is separate from the worker scheduler experiment.

The complete audit remains open. Final shared-executor timing must be collected
after every build has finished, using the complete prepared grid. Repeated
controls must cover content, eager metadata and slow storage. Native allocation
profiles and the separate 20k/50k/100k single-worker slope investigation still
need completion. Hosted macOS checks provide portability evidence for PR #991;
controls must cover content, eager metadata and slow storage. Separate evidence
in [PR #995](https://github.com/mboworks/xff/pull/995) now retains the exact-source
Linux baseline, enumeration-only allocation profiles and coarse counters/stacks
for 20k/50k/100k single-worker workloads. Those baseline diagnostics do not complete
precise phase-level attribution or allocation controls for the other workloads,
platforms or new scheduler candidates. Hosted macOS checks provide portability evidence for PR #991;
they do not replace the specified native M5 Pro A/B and sampling controls for
the shared-executor experiment.
78 changes: 55 additions & 23 deletions docs/design-parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ background workers under `--jobs=N`:
result handles in the order required by `--sort`. Directory workers start only when at least
64 sibling directories provide independent work. For a coordinator-read directory with at least
512 children and eagerly required metadata, 128-entry stat chunks use the same executor. The
coordinator also claims stat chunks. At most one drain job per background worker is queued for
each batch; worker jobs never wait on nested jobs. Metadata-free, lazy and
coordinator also claims stat chunks. Directory reads use a bounded ordered window with
command-wide execution and retained-storage limits; stat and content batches use at most one
drain job per participating background worker. Worker jobs never wait on nested jobs. Metadata-free, lazy and
owned/archive-source listings keep their serial per-directory path.
- The coordinator applies traversal controls and writes the output sink in traversal order.
Independent content predicates and rg content searches reuse that executor, with the caller also
Expand All @@ -77,8 +78,10 @@ background workers under `--jobs=N`:
workers share compiled patterns and reuse private match scratch, with bounded retained interpreter
heap frames and independent reentrant leases. Entry content and lazy metadata have one owner and
need no additional locks.
- A listing future may retain a completed directory read until the coordinator
reaches it. No worker emits a match and no traversal sort collects all matches.
- A listing result may retain a completed directory read until the coordinator
reaches it. Completed speculative results keep their storage charge but release
execution slots, so cached siblings do not by themselves prevent deeper reads.
No worker emits a match and no traversal sort collects all matches.

Comparison collects both trees sequentially on the same calling coordinator, reusing the
command's executor across the two walks. It then evaluates eligible large regular-file pairs
Expand Down Expand Up @@ -156,20 +159,47 @@ The caller claims independent stat, content and comparison chunks rather than
waiting idle while `N` additional workers run. Sequential comparison inventories
remove the second coordinator, but may reduce overlap when each tree has a
narrow frontier on slow storage; native A/B controls must quantify that tradeoff.
The executor still uses FIFO batch-drain jobs. A directory drain can occupy a
worker until its entire batch is exhausted, and completed read-ahead listings are
not bounded by a command-wide byte budget. Fair scheduling, bounded read-ahead,
cancellation, and useful-work admission based only on remaining work are still
required; a thread-count limit alone does not solve those problems. The cold
The bounded-frontier follow-up separates foreground stat/content/comparison jobs
from speculative directory reads. When both queues contain work, dispatch
alternates their classes, retaining FIFO order within each class. This is fairness
at job boundaries, not preemption: a large foreground drain or a blocking VFS call
still runs until it returns. Useful-work admission based only on remaining work,
task-size tuning and native timing/allocation validation remain required; the
thread-count and storage limits do not establish performance acceptance. The cold
matcher policy below is inherited for isolation, not endorsed by this change.

The coordinator admits directory read-ahead when at least 64 independent sibling
reads are available. It submits at most one drain job per worker; each job claims
successive directories from that batch and publishes indexed listing results.
Workers perform leaf reads and never recurse or wait on jobs in their own
executor. Eager stat work similarly uses drain jobs over 128-entry chunks after
at least 512 entries are available. This bounds executor queue operations by
worker count while preserving coordinator visitation order.
The coordinator currently admits directory read-ahead when at least 64 independent
sibling reads are available. This inherited count heuristic is provisional, not a
measurement of remaining cost. Each directory job performs one leaf VFS read plus
required metadata, publishes its result and yields before another directory.
Workers never recurse or wait on jobs in their own executor. Eager stat work still
uses drain jobs over 128-entry chunks after at least 512 entries are available.

Each admitted directory frontier keeps an ordered window of at most twice its
participating background-worker count. All nested and mounted-filesystem walks
also share a command-wide limit of twice the started background-worker count in
unfinished reads. A consumed parent request is not immediately refilled before
descent, allowing the deeper frontier to use available slots. Completion releases
an unfinished-read slot, while cached siblings keep their separate byte charges.

The experimental shared retained-storage allowance is 8 MiB. Every queued or
completed request first charges 512 bytes of bookkeeping plus its copied path;
completed speculative listings additionally charge vector/string capacities and
status storage. These conservative charges bound represented storage, not actual
allocator use or RSS; the 512-byte estimate and 8 MiB policy require native
allocation and performance controls. An unrepresentable or oversized speculative
listing is discarded and read on demand when visited. If the caller already needs
that result while its read is running, the required listing is transferred without
an additional lookahead charge, avoiding a second read. Required and ancestor
listings, in-flight VFS buffers and allocator overhead are outside this speculative
storage accounting; total traversal memory is not bounded by 8 MiB.

Reservations are released by their owners on consumption or destruction.
Per-request cancellation handles pruning, and a frontier-wide flag handles early
termination. Workers check both before and after each VFS read. Queued canceled
requests skip filesystem access; already running VFS calls cannot be interrupted.
The walker drains all its submitted jobs before its borrowed filesystem state
disappears, leaving the executor reusable by the next phase.

Content matching retains private evaluator and regex state for each logical
worker. Decision-only matching collects up to 8,192 entries before flushing;
Expand Down Expand Up @@ -214,14 +244,16 @@ design-config.md):
## Concurrency correctness

- **`-prune`** - the coordinator evaluates the entry and suppresses descent when
the visitor returns prune. Depending on the ordering mode, batched read-ahead
may read the pruned directory's listing before or after that decision, but no
entry from the pruned subtree is visited or acted upon.
the visitor returns prune. Known prune decisions are excluded from admission;
destroying an unused speculative request cancels it. A read already started
before that decision can still finish, but no entry from the pruned subtree is
visited or acted upon.
- **`-quit`** - the coordinator stops visiting entries immediately. Workers only
perform leaf directory reads and do not inspect the walk's stop flag; already
submitted drain jobs finish before their walker is destroyed, even when the
executor will be reused by a later phase. Their results are ignored,
and no new expression actions are run. Exit status follows the normal model.
perform leaf directory reads and poll cancellation flags around each VFS call.
Queued unused reads skip the filesystem; an in-flight call finishes before its
walker is destroyed, even when the executor will be reused by a later phase.
Their results are ignored, and no new expression actions are run. Exit status
follows the normal model.
- **`-depth` (post-order)** - children before parent. The ordering layer holds a
directory's own visit until its subtree has been emitted; under `none` this
still means a parent waits on its descendants' completion.
Expand Down
7 changes: 5 additions & 2 deletions xff/cli/globals.cc
Original file line number Diff line number Diff line change
Expand Up @@ -907,8 +907,11 @@ constexpr std::array kGlobals = std::to_array<GlobalFlag>({
"using one command-owned, lazily started pool of up to `N-1` background workers plus the calling "
"coordinator, reused for directory reads, required eager metadata, eligible content matching "
"and file comparison. Comparison trees are coordinated sequentially within that same budget. "
"The caller participates in eligible content and comparison work. Workers drain batches of independent "
"work; small workloads stay on the coordinator. Lazy and metadata-free listings do not start "
"The caller participates in eligible content and comparison work. Directory read-ahead uses a bounded "
"ordered window and a shared retained-storage allowance; oversized speculative listings are read on "
"demand instead. Unneeded queued reads are canceled after pruning or stopping, but an in-flight "
"filesystem call must finish. Workers reuse the same executor for independent work; small workloads "
"stay on the coordinator. Lazy and metadata-free listings do not start "
"stat workers. Independent content predicates and rg searches match in bounded parallel batches, "
"with output kept in traversal order. Small initial batches, archive members, stateful expressions "
"stay on the coordinator. Metadata-consuming output does not itself prevent eligible content "
Expand Down
4 changes: 4 additions & 0 deletions xff/engine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ cc_test(
deps = [
":run_executor_cc",
"@abseil-cpp//absl/synchronization",
"@abseil-cpp//absl/time",
"@googletest//:gtest",
"@googletest//:gtest_main",
],
Expand All @@ -58,6 +59,7 @@ cc_library(
"@abseil-cpp//absl/functional:function_ref",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings:cord",
"@abseil-cpp//absl/synchronization",
"@abseil-cpp//absl/types:span",
"@mboworks_mbo//mbo/status:status_macros_cc",
Expand Down Expand Up @@ -173,7 +175,9 @@ cc_test(
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/strings:cord",
"@abseil-cpp//absl/synchronization",
"@abseil-cpp//absl/time",
"@googletest//:gtest",
"@googletest//:gtest_main",
"@mboworks_mbo//mbo/testing:status_cc",
Expand Down
Loading
Loading