Skip to content
Merged
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: 2 additions & 0 deletions .github/actions/benchmark-measure/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ runs:
run: |
set -euo pipefail
tar -xf benchmark-build.tar -C "${RUNNER_TEMP}"
test -x "${RUNNER_TEMP}/benchmark-resource-launcher"
storage=(--fixture-parent="${RUNNER_TEMP}")
if [[ "${RUNNER_OS}" == Linux ]]; then
storage=(--fixture-parent=/dev/shm --require-memory --require-cpu-affinity)
Expand All @@ -59,6 +60,7 @@ runs:
cp "benchmark-report-${platform}.json" benchmark-shard.json
fi
python3 tools/benchmark_compare.py --binary="${RUNNER_TEMP}/benchmark-head" \
--resource-launcher="${RUNNER_TEMP}/benchmark-resource-launcher" \
--report=benchmark-shard.json --head="${BENCHMARK_HEAD}" \
--shard-plan=benchmark-plan.json --shard-index="${BENCHMARK_SHARD}" \
--cpus=1 --cpus=3 --repetitions=3 --keep=3 --require-tools "${storage[@]}" \
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/benchmark_backfill.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,20 @@ jobs:
timeout-minutes: 165
run: |
set -euo pipefail
# Build the current accounting adapter before the driver freezes the batch.
bazel build //tools:process_resources_native --config=clang_release \
--disk_cache="${HOME}/.cache/bazel-disk"
launcher="$(bazel cquery //tools:process_resources_native --config=clang_release \
--output=starlark --starlark:expr='target.files_to_run.executable.path')"
cp "${launcher}" "${RUNNER_TEMP}/benchmark-resource-launcher"
fixture_parent="${RUNNER_TEMP}"
if [[ "${RUNNER_OS}" == Linux ]]; then
fixture_parent=/dev/shm
fi
python3 tools/benchmark_campaign.py run --repo="${GITHUB_WORKSPACE}" \
--plan=campaign/campaign.json --job="${{ matrix.id }}" \
--output="${RUNNER_TEMP}/backfill" --fixture-parent="${fixture_parent}" \
--resource-launcher="${RUNNER_TEMP}/benchmark-resource-launcher" \
--disk-cache="${HOME}/.cache/bazel-disk"
- uses: actions/upload-artifact@v7
if: always()
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ jobs:
--disk_cache="${HOME}/.cache/bazel-disk"
cp bazel-bin/xff/cli/xff "${RUNNER_TEMP}/benchmark-head"
cp bazel-bin/xff/engine/read_benchmark "${RUNNER_TEMP}/benchmark-head-reads"
launcher="$(bazel cquery //tools:process_resources_native --config=clang_release \
--output=starlark --starlark:expr='target.files_to_run.executable.path')"
cp "${launcher}" "${RUNNER_TEMP}/benchmark-resource-launcher"
driver="$(pwd)/$(bazel cquery //tools:benchmark_resources --config=clang_release \
--output=starlark --starlark:expr='target.files_to_run.executable.path')"
(
Expand Down Expand Up @@ -141,7 +144,7 @@ jobs:
python3 tools/benchmark_shards.py --plan --adaptive --count=5 "${scales[@]}" \
--partition=samples --repetitions=15 --keep=15 \
--cpus=1 --cpus=${{ matrix.cpus }} --output=benchmark-plan.json
tar -cf benchmark-build.tar -C "${RUNNER_TEMP}" benchmark-head
tar -cf benchmark-build.tar -C "${RUNNER_TEMP}" benchmark-head benchmark-resource-launcher
- uses: actions/upload-artifact@v7
with:
name: benchmark-build-${{ matrix.platform }}
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -750,9 +750,12 @@ jobs:
namespace: default
- name: Build one optimized binary for all three measurement shards
run: |
bazel build //xff/cli:xff //xff/engine:expression_benchmark //xff/engine:expression_run_benchmark --config=clang_release --disk_cache="${HOME}/.cache/bazel-disk"
bazel build //xff/cli:xff //tools:process_resources_native //xff/engine:expression_benchmark //xff/engine:expression_run_benchmark --config=clang_release --disk_cache="${HOME}/.cache/bazel-disk"
cp bazel-bin/xff/cli/xff "${RUNNER_TEMP}/benchmark-head"
tar -cf benchmark-build.tar -C "${RUNNER_TEMP}" benchmark-head
launcher="$(bazel cquery //tools:process_resources_native --config=clang_release \
--output=starlark --starlark:expr='target.files_to_run.executable.path')"
cp "${launcher}" "${RUNNER_TEMP}/benchmark-resource-launcher"
tar -cf benchmark-build.tar -C "${RUNNER_TEMP}" benchmark-head benchmark-resource-launcher
- name: Measure expression preparation and execution baseline
run: |
bazel run //xff/engine:expression_benchmark --config=clang_release --disk_cache="${HOME}/.cache/bazel-disk" -- \
Expand Down
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ filegroup(
".github/actions/bazel-cache-restore/action.yml",
".github/actions/bazel-cache-save/action.yml",
".github/actions/benchmark-measure/action.yml",
".github/workflows/benchmark_backfill.yml",
".github/workflows/benchmark_pages.yml",
".github/workflows/benchmarks.yml",
".github/workflows/cache_size.yml",
Expand Down
9 changes: 7 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,13 @@ Run all tooling suites with `bazel test //tools:python_tests`; they also run und
The benchmark integration is `//xff/engine:read_benchmark_test`. Run the measurement commands with
`bazel run //tools:measure_resources -- -- COMMAND...` or
`bazel run //tools:benchmark_resources -- XFF --output=REPORT.json`.
The benchmark driver declares its measurement executable and runs each measurement in a fresh
process, preserving per-invocation peak-memory accounting. Direct script invocation remains available.
The benchmark driver declares its measurement executable and a small native pipeline launcher.
Each measurement uses a fresh process, and the native launcher accounts for individual commands
after leaving Python's address space. RSS remains a kernel high-water mark with the native
pre-exec footprint included; pipeline peak sums are not simultaneous peaks. Accounting methods
are recorded separately so they cannot share incompatible timing baselines. Direct script
invocation remains available with a prebuilt `--resource-launcher`; see
[native process accounting](docs/benchmark-comparisons.md#native-process-accounting).

Pre-commit owns changed-file formatting and policy checks, plus fast, narrowly triggered checker
self-tests. Its Python hooks use the managed Python 3.13 environment. Process-measurement and fuzz
Expand Down
51 changes: 51 additions & 0 deletions docs/benchmark-comparisons.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,57 @@ downloaded, and hosted CI remains unchanged until the local measurements support

## Tasks and equivalence

### Native process accounting

New collections use a small native pipeline launcher after the fresh Python
worker. The native process forks the measured commands only after exec has
discarded Python's address space; its own `wait4` usage is not reported as a tool
measurement. Each direct command has an individual RSS high-water mark. Pipeline
memory remains the sum of those individual peaks, an upper bound rather than a
simultaneous peak. The kernel can still include the smaller native child's
pre-exec footprint, so very small-command RSS needs a native control before
attributing it to the command's allocations.

The extra native launch boundary is deliberately included in elapsed and
first-output wall time. CPU usage and peaks account for the measured commands,
including their native pre-exec setup, not the accounting parent. New reports
record the launcher binary hash and the
memory/wall-time method in `contract.resource_accounting`; the full executable
path is retained separately in `resource_launcher`, and merged shard provenance
preserves each host's launcher identity. Different accounting methods
or launcher hashes cannot share main baselines or normalization windows. Older
reports remain unchanged and renderable, and dataset catalogs retain both methods
and their observations under the same workload recipe. Do not infer an engine
speedup from an old-method/new-method timing comparison.

Bazel declares and builds the launcher before executing measurement tools. Direct
script use requires an explicit prebuilt binary; measurement never triggers a
build. For example, prepare it before any timing session:

```sh
bazel build //tools:process_resources_native --config=clang_release
launcher=$(bazel cquery //tools:process_resources_native --config=clang_release --output=files)
python3.13 tools/benchmark_compare.py --resource-launcher="$PWD/$launcher" \
--binary=/path/to/optimized/xff --report=/tmp/native-comparison.json --cpus=1
```

The PR and main CI build jobs retain the optimized accounting helper alongside
XFF in `benchmark-build.tar`. Every measurement shard consumes that same
prebuilt helper through `--resource-launcher`; it does not run Bazel. Hosted
historical backfill builds and copies the current helper before freezing its
batch and building historical XFF revisions. This separates the accounting
adapter's identity from the measured source revision and keeps compilation
outside the measurement phase on both Linux and macOS.

The standalone resource tool, backfill driver and local benchmark command also
accept `--resource-launcher`. Frozen backfill contracts include the actual
launcher identity and both accounting source files. Changing either the driver
or launcher requires a new batch; it cannot retrofit memory measurements in an
existing batch. The launcher inherits the fresh worker's affinity and process
group, so the comparison timeout still terminates the complete pipeline.

### Task populations and output

| Task | Participants | Contract |
| ---------------------- | ---------------------- | ----------------------------------------------------- |
| File enumeration | xff, find, rg | All regular files, including hidden and ignored names |
Expand Down
Loading
Loading