Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
277e987
Reuse bounded matcher output capacities across batches
helly25 Nov 2, 2026
be8b8f7
perf: limit warmed matcher participation to available chunks
helly25 Nov 2, 2026
634b85e
Merge current main after bounded matcher result reuse
helly25 Nov 2, 2026
bcfef58
docs: retain rejected direct evaluator worker qualification
helly25 Nov 2, 2026
cfc21a6
Merge current main after useful matcher worker qualification
helly25 Nov 2, 2026
374a309
perf: carry requested content values to ordered consumers
helly25 Nov 2, 2026
ed9763f
test: verify parallel columns and line histogram cache reuse
helly25 Nov 2, 2026
a4a2207
Merge current main after evaluator qualification report
helly25 Nov 2, 2026
563a5c1
Reuse immutable default catalogs across library runs
helly25 Nov 2, 2026
7acbf03
Synchronize catalog reuse with merged main
helly25 Nov 2, 2026
bc4294e
Prepare immutable file-diff configuration once per style
helly25 Nov 2, 2026
77abeac
Synchronize diff configuration with merged main
helly25 Nov 2, 2026
327ea78
Prepare compact hash-primary operands once per expression
helly25 Nov 2, 2026
4e77548
Merge main after prepared diff configuration lands
helly25 Nov 2, 2026
cc00b95
Aggregate ordinary comparison side totals directly
helly25 Nov 2, 2026
ae7ec80
Merge main after prepared hash operands land
helly25 Nov 2, 2026
d528de1
Merge remote-tracking branch 'origin/main' into perf/borrow-normalize…
helly25 Nov 2, 2026
0f22d63
Reclaim unused hosted Android SDK space before LLVM extraction
helly25 Nov 2, 2026
fdc2c22
Merge remote-tracking branch 'origin/ci/free-llvm-extraction-space' i…
helly25 Nov 2, 2026
00b5fa0
Clear removed Android SDK discovery variables for native Bazel builds
helly25 Nov 2, 2026
18d57ba
Merge remote-tracking branch 'origin/ci/free-llvm-extraction-space' i…
helly25 Nov 2, 2026
8f86055
perf: reduce byte-word shingle allocation and copying
helly25 Nov 2, 2026
214ed91
Merge remote-tracking branch 'origin/main' into perf/borrow-normalize…
helly25 Nov 2, 2026
1a210e6
test: allow instrumented byte-reference comparisons to finish
helly25 Nov 2, 2026
35f404a
Reuse caller-owned archive input storage across callbacks
helly25 Nov 2, 2026
37c8033
Merge current main dependency updates into archive buffer work
helly25 Nov 3, 2026
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 .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ repos:
hooks:
- id: check-added-large-files
# Retain the exact lossless cross-platform performance evidence archives.
exclude: ^docs/benchmarks/((executor-copy-audit|bounded-read-ahead)-20261101|(directory-task-slots|packed-visit-snapshots|bounded-match-results|useful-match-workers|direct-evaluator-workers|derived-content-handoff|immutable-default-catalogs|prepared-diff-configuration|prepared-hash-operands|aggregate-comparison-totals|byte-shingle-representations)-20261102)\.json\.gz$
exclude: ^docs/benchmarks/((executor-copy-audit|bounded-read-ahead)-20261101|(directory-task-slots|packed-visit-snapshots|bounded-match-results|useful-match-workers|direct-evaluator-workers|derived-content-handoff|immutable-default-catalogs|prepared-diff-configuration|prepared-hash-operands|aggregate-comparison-totals|byte-shingle-representations|archive-input-buffers)-20261102)\.json\.gz$
- id: check-merge-conflict
- id: check-yaml
# BINARY_TYPES below: archive containers are binary but can START with text (a phar begins with
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

# 0.11.0

- Reuse a session-owned archive input buffer when the source supports caller-owned reads, preserving decoder fallbacks and read budgets.

- Avoid repeated word-window string construction in text similarity checks while preserving exact byte-word scores.

- Aggregate ordinary comparison side totals without retaining duplicate per-path summary tables; category and effect-sensitive summaries keep their existing behavior.
Expand Down
5 changes: 4 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,10 @@ EP/JC/P work; candidates are not speedup claims until measured on both platforms
caller-inclusive/admission policies. Preserve leaf jobs, explicit CPU/role accounting and JC gates.
- [ ] O18: Qualify byte-bounded overlap with backpressure, ordered publication and cancellation;
avoid per-batch throwaway threads and nested waits on the same saturated pool.
- [ ] O19: Qualify archive seeking/index/replay and decoder admission independently. Investigate
- [x] O19a: Qualify reusable session-owned archive input buffers on Linux/macOS, retaining short
reads, errors, decoder fallbacks and shared reservations. Preserve all representation trials
and controls in [the qualification report](docs/benchmarks/archive-input-buffers-20261102.md).
- [ ] O19b: Qualify archive seeking/index/replay and decoder admission independently. Investigate
native/memory ZIP seeking before concurrency; preserve sequential-source and shared-budget fallbacks.
- [ ] O20: Qualify optional bulk metadata by backend capability with precise fallback, while
keeping metadata-free traversal free of extra probes and extended-stat work.
Expand Down
1 change: 1 addition & 0 deletions bazelmod/clang_tidy_targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ keeps the two sets identical.
"""

CLANG_TIDY_MANUAL_TARGETS = [
"//tools:archive_source_benchmark",
"//xff/cli:globals_benchmark",
"//xff/engine:read_accounting_test",
"//xff/engine:compare_benchmark",
Expand Down
Binary file not shown.
90 changes: 90 additions & 0 deletions docs/benchmarks/archive-input-buffers-20261102.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Reusable archive input buffers (2026-11-02)

## Decision

Retain a lazily allocated, session-owned `std::array<char, 65536>` for sources advertising caller-owned reads. Its allocation is uninitialized; only the returned byte count is exposed to libarchive. A borrowed view reaches the C callback and expires at the next refill. The existing owned-string path remains for decoders without `ReadInto`. Capability changes release the inactive storage so both buffer forms are not retained under one reservation. The session and its existing 64 KiB reservation retain the buffer until the reader is destroyed.

Reject counts beyond the supplied span before exposing bytes; preserve errors, short reads, EOF, format probes, seeking, member identity and shared budgets. There is no new cache, thread, lock, exception path, dependency, format parser, flag or file-I/O bypass. ZIP member indexing/seeking and decoder concurrency remain separate O19 work.

A separately measured reusable-string prototype avoids allocations too. The selected owned array removes its nested `resize_and_overwrite` callback and error mutation, with comparable or slightly better pilot timings. The original matrix, duplicate-baseline controls and three-way pilot remain available in the raw artifact.

## Measurement contracts

Parent `1a210e61da249e27314dc5fc9a779cba50eb8d72` includes #1059 and the isolated CI repair already merged in #1058. The selected runtime changes only `archive_reader.cc`; tests and the manual benchmark are common to every variant. Both use extension-enabled `//xff/cli:xff_full`, `clang_release`/ThinLTO and recorded compiler/link/source/executable identities. These are paired changes on a fixed dependency set; later dependency updates require fresh qualification.

Per host: 76 native cases x three sessions over TAR, gzip TAR and ZIP with 10/1,000/10,000 members and 128/4,096-byte payloads, listing versus a literal-content test on the final member, one/three requested workers, and ordinary-file controls. Independent Python-generated fixtures and full stdout/zero exits are checked. Workers are requests, not a claim of newly parallel archive decoding. Linux uses tmpfs and CPU affinity; macOS uses warm APFS.

Separate in-memory timing has 24 cases x three sessions x two variants, with legacy and caller-owned sources. Each case has nine rounds/repetitions, fastest seven retained and discarded warmup. Confirmation uses eight native cases x five sessions and seven memory cases x three sessions, adding an identical-baseline participant. Timings never overlap builds, lint, sanitizers or allocation probes on their host. A filename-aware formatting gate checks every variant before building. Earlier include ordering produced different binaries, so the complete matrix and confirmation were rerun on the canonical source rather than reusing those timings.

## Native commands

Median paired session change; negative means less elapsed time.

| Fixture / action | CPUs | Linux | macOS |
| :-------------------- | ---: | -----: | -----: |
| tar-10-128 / list | 1 | -1.85% | +0.37% |
| tar-10000-128 / list | 1 | -1.69% | -1.76% |
| tar-10000-4096 / list | 1 | -2.50% | -4.99% |
| tar-10000-4096 / last | 3 | -2.61% | -6.94% |
| zip-10000-4096 / last | 1 | +0.41% | -0.19% |
| tgz-10000-4096 / last | 3 | +0.04% | +0.13% |
| ordinary / list | 1 | -0.07% | -1.09% |
| ordinary / last | 3 | -1.03% | -0.48% |

## Confirmation and controls

Candidate and byte-identical repeat each compare directly with baseline; repeat movement is not subtracted. Preserve slower small/ordinary controls as well as heavier TAR gains. ZIP/gzip work may remain decoder/header dominated.

| Fixture / action | CPUs | Confirm Linux | Repeat Linux | Confirm macOS | Repeat macOS |
| :-------------------- | ---: | ------------: | -----------: | ------------: | -----------: |
| ordinary / last | 3 | +2.43% | +0.56% | +1.18% | +1.12% |
| tar-10000-4096 / list | 1 | -2.26% | +0.05% | -3.55% | -0.12% |
| zip-10000-4096 / last | 1 | +0.89% | +0.18% | +0.18% | +0.10% |
| ordinary / list | 1 | +0.27% | +0.11% | +1.87% | -0.93% |
| tgz-10000-4096 / last | 3 | -0.04% | -0.09% | -0.03% | -0.22% |
| tar-10-128 / list | 1 | -0.08% | +0.70% | +0.02% | +0.38% |
| tar-10000-128 / list | 1 | -0.74% | +0.49% | -0.40% | +0.26% |
| tar-10000-4096 / last | 3 | -1.96% | -0.71% | -5.51% | -0.05% |

## In-memory callbacks

Values are microseconds: median session fastest-seven repetition means. Fixture creation is outside the timed iteration. Legacy sources retain their existing owned-read path; a different executable can still move their timings.

| Source / action / members / bytes | Linux baseline | Linux selected | Linux delta | macOS baseline | macOS selected | macOS delta |
| :-------------------------------- | -------------: | -------------: | ----------: | -------------: | -------------: | ----------: |
| caller-owned/list/10000/4096 | 4,286.44 | 3,997.75 | -6.73% | 3,957.20 | 3,957.09 | -0.00% |
| caller-owned/last/10000/4096 | 3,843.83 | 3,629.33 | -5.58% | 3,768.58 | 3,770.62 | +0.05% |
| caller-owned/list/10/128 | 7.89 | 8.11 | +2.75% | 6.62 | 6.69 | +0.99% |
| legacy/list/10000/4096 | 4,263.61 | 3,956.39 | -7.21% | 3,965.36 | 3,991.04 | +0.65% |
| legacy/last/10000/128 | 2,395.93 | 2,134.93 | -10.89% | 2,980.26 | 2,980.54 | +0.01% |

## Allocation diagnostics

Separate warmed calling-thread ASan hooks count requests/bytes only. These are neither release timings nor peak/RSS measurements; fixture setup and reporting are excluded. Session input-buffer allocations no longer scale with chunk count on the caller-owned path; member records still allocate.

| Case | Host | Baseline requests | Selected requests | Baseline requested bytes | Selected requested bytes |
| :--------------------------- | :---- | ----------------: | ----------------: | -----------------------: | -----------------------: |
| caller-owned/list/10000/4096 | linux | 60,742 | 60,039 | 50,601,064 | 4,574,320 |
| caller-owned/list/10000/4096 | macos | 60,742 | 60,039 | 50,595,400 | 4,574,288 |
| caller-owned/last/10000/4096 | linux | 60,730 | 60,027 | 48,377,040 | 2,350,296 |
| caller-owned/last/10000/4096 | macos | 60,730 | 60,027 | 48,371,352 | 2,350,240 |
| legacy/list/10000/4096 | linux | 60,742 | 60,742 | 50,601,064 | 50,601,072 |
| legacy/list/10000/4096 | macos | 60,742 | 60,742 | 50,595,400 | 50,595,408 |

## Correctness and footprint

Nine new behavior cases verify stable storage, short reads, late failures, impossible counts, EOF, unchanged legacy fallback, compression, alternating read capabilities and budget sharing/release. All 324 extension-enabled release suites pass per host, with required Linux FUSE. Four focused ASan suites pass per host; four Linux TSan suites and five Linux MSan suites pass, including the similarity reference test with its corrected timeout. Exact selected-source checks precede final validation. Scoped lint and enforcing hooks remain required; current pushed-head CI determines readiness.

Observed maximum process RSS for the 10,000-member, 4,096-byte TAR listing across all native samples; this is separate from cumulative allocation requests.

| Host | Baseline RSS MiB | Selected RSS MiB |
| :---- | ---------------: | ---------------: |
| linux | 13.04 | 13.06 |
| macos | 13.56 | 14.28 |

- linux: unstripped full CLI 18,118,000 -> 18,121,664 bytes (+3,664).
- macos: unstripped full CLI 8,140,216 -> 8,140,216 bytes (+0).

## Lossless evidence

[archive-input-buffers-20261102.json.gz](archive-input-buffers-20261102.json.gz): 5,196,448 bytes; SHA-256 `8ee9b6ccdf8656162ff129fa1440d5ec4826be240a9e800d46e6f5089fcedf93`.
27 changes: 27 additions & 0 deletions docs/performance-analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -3455,6 +3455,33 @@ own saturated pool. O08 task storage and O11 warm participation do not establish
executor is faster. Global sorting/effects/deferred/archive cases need explicit fallback contracts
until independently qualified.

### O19a: reuse archive input storage

`SourceSession::Read` previously requested a new owned string on each raw 64 KiB callback even
when its cursor supported caller-owned reads. The selected path lazily owns one uninitialized
`std::array` and passes only initialized bytes to libarchive. Its existing shared memory reservation
is unchanged. Sequential decoders without that capability retain their owned-string fallback;
short reads remain distinct from EOF, and impossible counts fail before exposing the buffer.
If a cursor changes its advertised read capability, release the inactive buffer before switching
storage forms so the single reservation does not retain both allocations.

[The qualification report](benchmarks/archive-input-buffers-20261102.md) keeps native TAR/gzip/ZIP
workloads, in-memory caller-owned and legacy controls, identical-baseline confirmation, allocation
diagnostics, exact source identities and binary sizes separate. The reusable-string prototype and
the simpler owned-array pilot are retained. Removing allocation churn does not remove header,
index or decoder costs, and ordinary/small controls can remain slower. Requested workers do not
mean archive decoding became parallel.

Format each frozen variant with its actual filename before building. Formatting standard input
without that filename can change main-header ordering; in this experiment it changed the binary
hashes. The canonical matrix and confirmation are rerun rather than transferring old timing claims.
The builder now verifies filename-aware formatting before any variant build, and final correctness
validation checks the exact restored source. Counts from instrumented allocators remain diagnostics,
never release timing or peak-memory evidence.

O19b random-access capability, ZIP seeking/indexing, nested replay and decoder admission remain
open. This change adds none of them and does not claim constant-time member lookup.

### O21a: byte-word shingle representations

The exact verifier previously built a vector of owning words and another owning string for every
Expand Down
18 changes: 18 additions & 0 deletions extra_modules/archive/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,24 @@ cc_test(
# libarchive's write API, then reads it back through our reader, so it proves the dependency links,
# the format/filter detection works, and the header fields map correctly - without committing binary
# fixtures. Also pins the two distinct error states (not an archive vs. corrupt archive).
cc_test(
name = "archive_source_buffer_test",
size = "small",
srcs = ["archive_source_buffer_test.cc"],
deps = [
":archive_reader_cc",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@googletest//:gtest",
"@googletest//:gtest_main",
"@libarchive//libarchive",
"@mboworks_mbo//mbo/testing:matchers_cc",
"@mboworks_mbo//mbo/testing:status_cc",
"@xff_extras_api//:read_budget_cc",
"@xff_extras_api//:read_source_cc",
],
)

cc_test(
name = "archive_reader_test",
size = "small",
Expand Down
29 changes: 24 additions & 5 deletions extra_modules/archive/archive_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <limits>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
Expand Down Expand Up @@ -204,12 +205,31 @@ struct SourceSession {
vfs::ReadBudget::Reservation reservation;
std::unique_ptr<vfs::ReadStream> stream;
std::string buffer;
std::unique_ptr<std::array<char, kBlockSize>> read_buffer;
std::string prefix;
absl::Status error;
std::optional<std::uint64_t> size;
// Declared last so the reader is released before its callback state.
ArchivePtr handle;

// Views remain valid until the next refill or session destruction.
absl::StatusOr<std::string_view> Refill() {
if (!stream->SupportsReadInto()) {
read_buffer.reset();
MBO_ASSIGN_OR_RETURN(buffer, stream->Read(kBlockSize));
return std::string_view(buffer);
}
if (!read_buffer) {
std::string{}.swap(buffer);
read_buffer = std::make_unique_for_overwrite<std::array<char, kBlockSize>>();
}
MBO_ASSIGN_OR_RETURN(const auto count, stream->ReadInto(std::span<char>(*read_buffer)));
if (count > kBlockSize) {
return absl::DataLossError("archive source returned more bytes than its read buffer");
}
return std::string_view(read_buffer->data(), count);
}

// XFF_ABI_POINTER: XAR requests absolute heap positions through libarchive's C callback.
static la_int64_t Seek(struct ::archive*, void* data, la_int64_t offset, int whence) {
auto& self = *static_cast<SourceSession*>(data);
Expand Down Expand Up @@ -244,17 +264,16 @@ struct SourceSession {
// XFF_ABI_POINTER: libarchive's input callback uses opaque state and a borrowed byte buffer.
static la_ssize_t Read(struct ::archive*, void* data, const void** buffer) {
auto& self = *static_cast<SourceSession*>(data);
auto result = self.stream->Read(kBlockSize);
auto result = self.Refill();
if (!result.ok()) {
self.error = result.status();
return -1;
}
self.buffer = std::move(*result);
if (self.prefix.size() < 4) {
self.prefix.append(self.buffer, 0, 4 - self.prefix.size());
self.prefix.append(*result, 0, 4 - self.prefix.size());
}
*buffer = self.buffer.data();
return static_cast<la_ssize_t>(self.buffer.size());
*buffer = result->data();
return static_cast<la_ssize_t>(result->size());
}
};

Expand Down
Loading
Loading