Skip to content

perf: bound metadata reads for large media containers - #4

Merged
vbasky merged 37 commits into
vbasky:mainfrom
Else00:revelo-streaming-random-access
Jun 29, 2026
Merged

vbasky merged 37 commits into
vbasky:mainfrom
Else00:revelo-streaming-random-access

Conversation

@Else00

@Else00 Else00 commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR finishes the large-file performance fix started by
issue #1 and the 0.5.0 release.

0.5.0 fixed the first layer of the problem: Metadata::from_file now uses mmap
instead of std::fs::read, from_file_owned preserves the old full-read path,
and ByteSource / ReadBackend made the input model explicit. That removed
the API-level full-buffer allocation.

The remaining issue is inside parser traversal. Several container and
codec config parser paths could still request or copy file-size-proportional ranges
after the file was mapped. That means the mmap path avoided allocating the whole
file up front, but large MP4/MOV, RIFF/FORM, Matroska/WebM and tag/config boxes
could still behave proportionally to payload size.

This branch keeps Revelo's current parser architecture: parsers still walk real
container structures and use random access where the format requires it. The
change is that metadata parsing now skips bulk payloads and caps metadata/config
payload reads.

Key changes:

  • add measurable access accounting (bytes_requested, copied bytes, range
    count, max window) so large-file regressions are asserted directly, not only
    by wall-clock time;
  • cap or skip large raw ranges across detector and parser paths instead of
    copying full payloads into temporary buffers;
  • keep the typed MediaReadAt / FileBackend random-access boundary as
    groundwork for future seekable-window backends; this PR still parses through
    the existing slice/mmap window paths and does not claim non-seekable streaming;
  • bound MP4/MOV metadata and codec config reads, including ilst/data, avcC,
    hvcC, esds, mp4a, moov front/tail and PS5-like SNV2 layouts;
  • bound RIFF/FORM style scans for WAV/RF64/AIFF and parse RF64 ds64 sizes
    without reading sample payloads;
  • bound Matroska/WebM, Ogg/Vorbis, FLAC, MP3 ID3v2, MPEG-TS, MPEG-PS and AVI
    metadata scans where they previously could scale with payload size;
  • extend the Criterion matrix with structured small and large/sparse fixtures
    for MP4/MOV/SNV2, WAV/RF64/AIFF, WebM/Matroska, FLAC, MP3, Ogg/Vorbis,
    MPEG-TS and MPEG-PS.

Benchmark matrix

revelo-pr-benchmark-table

revelo-pr-benchmark-table.json

Why 0.5.0 was not quite enough

The 0.5.0 benchmark harness was useful for proving the new mmap entry point
against the legacy full-read path, but it covered a small WAV and a 100 MiB
sparse WAV. A minimal ftyp + mdat MP4 row is also useful as a baseline, but
by itself it does not exercise realistic moov, ilst, codec configuration or
PS5/SNV2 brand layout.

That missed the case described in issue #1: the 0.5.0 response expected a
389 MiB MP4 to probe in tens of milliseconds after mmap, comparable to
MediaInfo. On a generated PS5-like SNV2 MP4 with the same scale, 0.5.0 is
still essentially unchanged from 0.4.6:

Provider Mean Median Runs
Revelo 0.4.6 750.2 ms 756.7 ms 20
Revelo 0.5.0 737.4 ms 717.1 ms 20
This branch 103.5 ms 105.4 ms 20
MediaInfo 10.8 ms 10.6 ms 20
ffprobe 23.9 ms 22.3 ms 20

The same pattern appears on available real media. On a large MP4 real-media
row, 0.5.0 remains around 4.2 seconds while this branch is around 135 ms:

Provider Mean Runs
Revelo 0.4.6 4144.4 ms 10
Revelo 0.5.0 4205.8 ms 10
This branch 135.0 ms 10
MediaInfo 87.1 ms 10
ffprobe 76.7 ms 10

Benchmark Evidence

The screenshot above is a compact view of the cross-tool matrix. It contains
the active comparable rows only: 16 structured synthetic large-file fixtures
and 16 real-media rows with sanitized labels.

The synthetic rows are intentionally parser-structured stress cases. They prove
that metadata access is bounded for layouts that used to be easy to miss:
MP4/MOV/SNV2 moov front/tail, large mdat, metadata boxes, codec config,
Matroska/WebM clusters, MPEG transport/program streams and common audio/tag
containers.

The real-media rows show the same behavior on actual files, but they are not a
perfectly balanced public corpus. The strongest real coverage is MP4, MPEG-TS,
M2TS, VOB and AVI; available WAV/AIFF/RF64 real files are small or medium, so
long-audio stress is covered by generated fixtures and access-stat regressions.

Tools:

  • Revelo 0.4.6
  • Revelo 0.5.0
  • this branch at 157ea51 for the full cross-tool matrix; current pushed head
    is ecdcb9c
  • MediaInfo 26.05
  • ffprobe 8.1.2
  • hyperfine 1.20.0

Benchmark environment:

  • Machine: MacBook Pro (Mac15,9), Apple M3 Max, 16-core CPU
    (12 performance + 4 efficiency), 48 GB RAM
  • OS: macOS 26.5.1 (25F80), Darwin 25.5.0, arm64
  • Rust: rustc/cargo 1.96.0, stable-aarch64-apple-darwin, LLVM 22.1.2
  • Benchmark runner: hyperfine 1.20.0
  • Reference tools: MediaInfo 26.05, ffprobe 8.1.2

Representative real-media rows, 2 warmups + 10 measured runs:

File class Revelo 0.4.6 mean Revelo 0.5.0 mean This branch mean MediaInfo mean ffprobe mean
MP4 AVC, large real row 4144.4 ms 4205.8 ms 135.0 ms 87.1 ms 76.7 ms
MOV/MP4, medium real row 916.1 ms 888.1 ms 45.5 ms 19.4 ms 34.5 ms
MKV DTS, medium real row 447.8 ms 449.6 ms 36.9 ms 18.9 ms 42.3 ms
MPEG-TS AC-4, large real row 1454.7 ms 1433.3 ms 60.2 ms 328.7 ms 72.6 ms
AVI WMA, medium real row 628.2 ms 593.5 ms 41.0 ms 181.3 ms 31.6 ms

Notes:

  • MediaInfo remains faster on many BMFF/audio rows; the goal here is to remove
    Revelo's file-size-proportional behavior while preserving Revelo's parser
    philosophy.
  • Commits after the full cross-tool run add the final CLI single-file mmap path,
    the audio RIFF/FORM bound fix and documentation clarification. I kept those
    single-provider spot checks out of the screenshot because they are regression
    checks, not provider comparisons.
  • Current-head spot checks keep RF64, WAV, AIFF, AIFF-C and PS5/SNV2 metadata
    cases around 24-26 ms over 10 measured runs.
  • The remaining PS5 branch-vs-MediaInfo gap is not explained by text, summary,
    JSON or verify output mode; those slices were all around 105-109 ms.
  • TAR detection now requires a complete 512-byte header instead of treating
    shorter inputs as TAR candidates. That removes a false-positive path; real TAR
    archives are block-based and at least one 512-byte block.

Threshold rationale:

  • The new caps are parser invariants, not user-facing knobs. Keeping them as
    internal constants makes benchmark results and access-stat regressions
    deterministic across CLI, library, C ABI and WASM callers.
  • The values are intentionally grouped by data role: short text metadata uses
    small KiB-scale bounds, codec/private config gets larger 256 KiB-style bounds,
    elementary stream probes get MiB-scale bounds, and format detection gets an
    8 MiB raw-read cap only to prevent speculative candidates from touching an
    entire large mmap before the winning parser is selected.
  • If an oracle corpus shows a real metadata field needs a larger bound, the
    intended change is to raise the relevant parser constant and add a fixture or
    access-stat regression for that format, not to make the cap runtime
    configurable.

Tests

Existing behavior is covered by the full workspace test suite, which passes on
this branch. The new coverage is focused on keeping the performance fix
functional instead of only timing-based:

  • core FileAnalyze access accounting tests verify bytes_requested,
    returned/copied bytes, request count and max request window;
  • parser regressions assert metadata-only budgets for structured MP4/MOV/SNV2,
    sparse WebM/Matroska, WAV/RF64/AIFF, FLAC, MP3 ID3v2, Ogg/Vorbis, MPEG-TS and
    MPEG-PS;
  • existing parser unit tests still exercise the normal small-file behavior and
    format extraction paths;
  • the Criterion benchmark target compiles with the new generated fixture
    matrix, so the benchmark cases are not just local scripts.

Commands run:

  • cargo fmt --all --check
  • cargo test --workspace --all-targets
  • cargo test -p revelo --bench parse_benchmark --no-run
  • cargo build -p revelo --release

senpai added 30 commits June 28, 2026 00:46
Introduce typed byte ranges, read-at errors, source backends, access stats, and a large-input detection cap without removing the existing parser-facing compatibility path. This gives the PR an incremental source boundary while preserving from_bytes/from_file behavior.

Constraint: Keep existing parser APIs and small-file ergonomics working while introducing a random-access boundary

Rejected: Rewrite FileAnalyze fully around the new trait in this checkpoint | too much churn before parser migrations are independently bounded

Confidence: medium

Scope-risk: moderate

Directive: New parser work should prefer bounded windows and measured access; do not add implicit full-slice reads to hot paths

Tested: cargo test -p revelo-core -p revelo --all-targets

Tested: cargo test -p revelo-core -p revelo --no-default-features --all-targets

Tested: cargo bench -p revelo --bench parse_benchmark -- from_file_mmap/large_mp4_sparse/100 MiB

Not-tested: MediaInfo/ffprobe comparison on real external corpus
Replace full-buffer raw peeks in archive, tag, and simple audio/video/text detectors with exact header reads or explicit small scan caps. Add local regression guards for migrated archive and tag parser families.

Constraint: Keep parser signatures and existing fixture behavior unchanged

Rejected: Add a repo-wide lint script in this checkpoint | useful later, but module-local tests are less disruptive while parser migration is incomplete

Confidence: medium

Scope-risk: moderate

Directive: Header-only detectors should request only the bytes needed for their magic/header checks

Tested: cargo test -p revelo-parsers-archive -p revelo-parsers-tag -p revelo-parsers-audio -p revelo-parsers-text -p revelo-parsers-video --all-targets

Tested: git diff --cached --check

Not-tested: Real-world corpus parity for every migrated detector
Convert simple audio detector probes from full-buffer raw peeks to exact header windows. This keeps existing parser behavior while reducing accidental read pressure during broad format detection.

Constraint: Preserve current parser signatures and fixture expectations

Rejected: Batch complex bitstream parsers with this change | their full-buffer scans need format-specific limits and separate review

Confidence: high

Scope-risk: narrow

Directive: Keep simple magic/header detectors on exact byte windows

Tested: cargo test -p revelo-parsers-audio --all-targets

Tested: git diff --cached --check

Not-tested: External corpus parity for the touched audio detectors
Convert simple video detector probes to exact header windows, including VC-3's fixed header prefix region. This removes full-buffer peeks from header-only detection paths without changing parser output.

Constraint: Stay within existing parser style and avoid shared helpers for a small mechanical batch

Rejected: Touch AVC/HEVC/VVC/AV1 in this commit | those parsers need format-specific scan budgets

Confidence: high

Scope-risk: narrow

Directive: Keep fixed-header video detectors on exact windows; use separate commits for bitstream scans

Tested: cargo test -p revelo-parsers-video --all-targets

Tested: git diff --cached --check

Not-tested: External corpus parity for the touched video detectors
Bound simple detector reads to the exact header or declared text window they inspect, so large payloads no longer get requested just to reject or identify a stream.

Constraint: Preserve existing parser signatures and small-file behavior while reducing raw buffer requests

Rejected: Move these detectors to the full source trait now | larger API migration belongs with the remaining complex bitstream parsers

Confidence: high

Scope-risk: narrow

Directive: Keep parser comments focused on format behavior; use access-stats tests for performance invariants

Tested: cargo test -p revelo-parsers-audio -p revelo-parsers-text -p revelo-parsers-video --all-targets

Tested: git diff --check
Keep PCM and Dolby E on exact header reads, and cap AC-4 and TrueHD inspection to bounded frame windows while retaining existing metadata extraction and full-size accounting.

Constraint: Preserve current parser outputs and public parser signatures

Rejected: Deep IAMF migration in this batch | it walks descriptor OBUs and needs a separate parser-specific pass

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce fa.remain() raw probes in simple audio detectors without an access-stats regression

Tested: cargo test -p revelo-parsers-audio --all-targets

Tested: git diff --check
Bound Dolby Vision XML, VVC NAL scanning, and ProRes frame header inspection so these parsers no longer request the complete payload before identifying metadata.

Constraint: Leave AVC/HEVC/AV1 for a dedicated bitstream pass because their scans extract SEI/HDR metadata across NAL units

Rejected: Treat ProRes MOV fallback as a full scan | the existing fallback rechecked the same frame offset and did not implement container traversal

Confidence: high

Scope-risk: narrow

Directive: Keep VVC scan caps conservative unless fixture evidence supports a smaller bound

Tested: cargo test -p revelo-parsers-video --all-targets

Tested: git diff --check
Limit IAMF OBU descriptor parsing to a bounded first-window copy, keeping metadata extraction intact while preventing large audio payloads from being requested wholesale.

Constraint: IAMF metadata OBUs are expected near the stream front; large frame payloads should not be part of detection

Rejected: Rewrite IAMF OBU iteration over MediaReadAt now | a streaming OBU walker is larger than this parser-cap pass

Confidence: medium

Scope-risk: narrow

Directive: Revisit IAMF scan limit with real corpus evidence before lowering it

Tested: cargo test -p revelo-parsers-audio --all-targets

Tested: git diff --check
Cap AVC, HEVC, and AV1 elementary-stream inspection to a conservative initial window, preserving the existing NAL/OBU walkers while eliminating implicit full-payload raw requests.

Constraint: Current parser architecture materializes NAL/OBU windows before walking metadata

Rejected: Rewrite NAL walkers over MediaReadAt in this commit | larger behavioral surface needs fixture-backed parity work

Confidence: medium

Scope-risk: moderate

Directive: Treat the 1 MiB limits as conservative defaults; tune only with real corpus evidence and HDR/SEI regression fixtures

Tested: cargo test -p revelo-parsers-video --all-targets

Tested: rg -n 'peek_raw\(fa\.remain\(\)\)|read_raw\(fa\.remain\(\)|peek_raw\(remain\)|read_raw\(remain\)' crates/revelo-parsers-audio crates/revelo-parsers-video crates/revelo-parsers-text

Tested: git diff --check
Add crate-level static tests for audio, video, and text parsers so the eliminated raw full-scan patterns cannot be reintroduced silently.

Constraint: Keep the guard dependency-free and local to parser crates

Rejected: Add an external lint script now | crate tests are simpler and run with the existing workspace test gate

Confidence: high

Scope-risk: narrow

Directive: Extend these guards when new parser crates migrate away from full-buffer raw probes

Tested: cargo test -p revelo-parsers-audio -p revelo-parsers-video -p revelo-parsers-text --all-targets

Tested: git diff --check
HEIF only needs the leading ftyp header, while MPEG-TS and MPEG-PS already operate as initial stream probes. Keeping those reads bounded prevents selected parsers from faulting or copying large mapped files before emitting core metadata.

Constraint: Preserve current parser shape without introducing a new streaming walker in this checkpoint

Rejected: Leave parser-level full reads to the dispatcher raw-read cap | the selected parser still needs bounded access

Confidence: high

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-image -p revelo-parsers-container --all-targets
TGA metadata comes from the fixed header, optional short image ID, and v2 footer signature. Reading those windows directly avoids pulling image payload bytes for large TGA assets.

Constraint: Keep existing emitted fields and v2 footer behavior

Rejected: Cap the original full-file read | it would still scale with payload size up to the cap

Confidence: high

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-image --all-targets tga
WebP only needs RIFF chunk headers plus short VP8/VP8L/VP8X payload prefixes for the fields Revelo emits. Walking chunks by offset lets large metadata chunks be skipped instead of loaded.

Constraint: Preserve existing lightweight WebP metadata extraction

Rejected: Fixed-size leading scan window | it can miss VP8 chunks after large metadata and still reads unnecessary bytes

Confidence: high

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-image --all-targets webp
AVI header, stream, INFO, and movi accounting can be derived from RIFF chunk headers plus small fixed payload prefixes. Offset walking avoids materializing the full RIFF body while preserving existing stream fields.

Constraint: Keep current Lavc encoder sniff bounded to the first 256 KiB

Rejected: Drop movi accounting entirely | existing StreamSize and interleave fields depend on chunk sizes

Confidence: medium

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-container --all-targets avi
EXR metadata lives in the bounded header attribute list before the sentinel. Walking names, types, and selected values by offset avoids reading image payload bytes while preserving emitted dimensions, compression, frame rate, and comments.

Constraint: Limit pathological header/comment reads without changing the public parser API

Rejected: Fixed leading file cap | it still reads unrelated payload and does not encode the EXR header boundary

Confidence: high

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-image --all-targets exr
TIFF metadata is encoded in fixed-size IFD entries with optional value offsets. Reading entries and selected values directly avoids loading pixel payloads for large TIFF files while preserving current field extraction.

Constraint: Keep the single-IFD parser shape and existing emitted fields

Rejected: Leading scan cap | TIFF value offsets can legitimately point outside the leading window

Confidence: high

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-image --all-targets tiff
Workbench icon metadata is parsed from compact legacy sections. A format-specific window prevents accidental full-file reads on oversized inputs while preserving the existing sequential parser.

Constraint: Avoid risky offset rewrite for the legacy icon walker in this checkpoint

Rejected: Keep full availability read | oversized icon-like files would still scale with payload size

Confidence: medium

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-image --all-targets amiga_icon
DAT, DTS, and AAC ADTS used file-size variables to request full buffers after header detection. DAT now inspects the first frame, DTS keeps header-derived metadata, and ADTS counts frames by reading only per-frame headers.

Constraint: Preserve file-size based StreamSize and duration calculations where they already existed

Rejected: Fixed full-buffer caps | ADTS can count frames accurately with offset header reads

Confidence: high

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-audio --all-targets
The previous static guard caught only inline fa.remain() raw reads. Extending it to common file-size variable names and adding image/container coverage prevents the indirect regressions fixed in this stream from returning silently.

Constraint: Keep the guard lexical and targeted so bounded variables such as window, want, chunk_size, and scan_len remain allowed

Rejected: Ban every variable-sized raw read | many structured parsers legitimately read bounded chunk windows

Confidence: high

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-audio -p revelo-parsers-video -p revelo-parsers-text -p revelo-parsers-image -p revelo-parsers-container --lib
Ogg now inspects only the BOS header window needed for codec detection, and MKV skips oversized text or CodecPrivate payloads instead of trusting arbitrary EBML sizes. The EBML walker also uses checked arithmetic for element bounds.

Constraint: Preserve existing slice-backed parser API while making payload materialization bounded.

Rejected: Parse full Ogg page and MKV CodecPrivate payloads unconditionally | retained avoidable large reads from format-declared sizes.

Confidence: high

Scope-risk: moderate

Directive: Do not raise MKV payload caps without adding fixture evidence for the required metadata.

Tested: cargo test -p revelo-parsers-container --all-targets
JPEG now peeks only the segment windows needed for SOF, JFIF, and comments, while PNG skips text chunks above the metadata parse cap. The stream-size accounting still counts full metadata chunks, but large payloads are no longer materialized just to inspect labels.

Constraint: Preserve existing metadata semantics for ordinary JPEG APP and PNG text chunks.

Rejected: Treat all declared image metadata payload sizes as trusted | allows large comments or text chunks to dominate parser cost.

Confidence: high

Scope-risk: narrow

Directive: EXIF parsing remains capped at the JPEG segment boundary; move deeper EXIF streaming to the tag parser if larger evidence requires it.

Tested: cargo test -p revelo-parsers-image --all-targets
Broadcast-WAV metadata now reads only the fixed bext prefix plus a bounded coding-history window. iXML and UMID chunks also use explicit parse caps before field extraction, while chunk-size accounting and cursor skipping still cover the full RIFF structure.

Constraint: Keep PCM/WAV parsing behavior and BWF field extraction intact for normal metadata chunks.

Rejected: Read every declared bext/iXML/UMID payload in full | large metadata chunks can dominate parse cost without improving core media metadata.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-container --all-targets wav
MP4 metadata boxes now skip oversized ilst values and keys instead of materializing arbitrary payloads. Handler names, mp4a extension scanning, and Dolby Vision config reads use bounded prefixes while preserving ordinary box parsing.

Constraint: Leave codec configuration parsers unchanged except fixed-size Dolby Vision fields; avcC/hvcC need dedicated fixture coverage before deeper caps.

Rejected: Keep scanning complete mp4a extension tails | malformed or oversized sample entries could still dominate parse cost before esds is found.

Confidence: medium

Scope-risk: moderate

Tested: cargo test -p revelo-parsers-container --all-targets mp4
FLAC VORBIS_COMMENT parsing now bounds vendor and user-comment strings before materializing them. Oversized values are skipped within the declared block, preserving cursor alignment and ordinary STREAMINFO behavior.

Constraint: Keep FLAC metadata extraction for normal Vorbis comments while avoiding length-declared large strings.

Rejected: Read every Vorbis comment string in full | arbitrary comment lengths can dominate parse cost and memory.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-audio --all-targets
The IBI EBML probe now caps DocType materialization and uses checked element bounds. Oversized DocType payloads are skipped while normal MediaInfo Index detection stays unchanged.

Constraint: IBI only needs the EBML DocType string for current metadata output.

Rejected: Reuse the declared DocType size blindly | malformed EBML can force an avoidable raw read.

Confidence: high

Scope-risk: narrow

Tested: cargo test -p revelo-parsers-container --all-targets ibi
MP4 metadata payloads were already bounded, but oversized avcC/hvcC boxes could still be materialized as complete raw bodies. Parse codec configuration from bounded prefixes and skip the remaining body so malformed or adversarial files cannot reintroduce file-proportional requests through codec private data.

Constraint: Codec config boxes are expected to be small but are attacker-controlled container payloads

Rejected: Leave avcC/hvcC uncapped until the larger fixture benchmark pass | would leave a known hole in the access-stat matrix

Confidence: high

Scope-risk: narrow

Directive: Do not raise MP4_AVCC_PARSE_LIMIT or MP4_HVCC_PARSE_LIMIT without adding a real sample or codec feature that needs the larger prefix

Tested: cargo fmt --all --check

Tested: cargo test -p revelo-parsers-container mp4 --all-targets
The previous synthetic large MP4 benchmark contained only ftyp plus mdat, so it could not represent the metadata-heavy files that exposed the regression. Add generated sparse MP4/MOV cases with moov at both ends, SNV2 branding, capped metadata, and oversized codec config boxes.

Constraint: Official benchmarks must stay synthetic and committable without private media

Rejected: Add real PS5 media to the repo | private file and too large for source control

Confidence: high

Scope-risk: narrow

Tested: cargo fmt --all --check

Tested: cargo test -p revelo --bench parse_benchmark --no-run

Tested: cargo bench -p revelo --bench parse_benchmark -- from_file_mmap/large_mp4_moov_front

Tested: cargo bench -p revelo --bench parse_benchmark -- from_file_mmap/large_mov_moov_tail

Tested: cargo bench -p revelo --bench parse_benchmark -- from_file_mmap/large_snv2_moov_tail

Not-tested: Full benchmark suite across all fixture families
The PR now has a committable MP4 regression that exercises front moov, tail moov and SNV2 tail layouts with large metadata and codec config boxes. The test asserts access cost, not elapsed time, so a future full-payload read fails deterministically.

Constraint: Regression evidence must not depend on private PS5 media

Rejected: Rely only on Criterion timing | timing cannot distinguish cache variance from structural byte access

Confidence: high

Scope-risk: narrow

Tested: cargo fmt --all --check

Tested: cargo test -p revelo-parsers-container mp4 --all-targets

Not-tested: MKV/WebM and WAV/AIFF/RF64 access-stat budgets
Add a synthetic WebM fixture with oversized tag value, large attachment data, large Cues and large Cluster payloads. The regression asserts requested and returned bytes stay below the metadata budget while preserving small metadata such as cover detection.

Constraint: MKV/WebM evidence must be committable and avoid large binary fixtures

Rejected: Use elapsed Criterion time for this guard | access stats catch the structural regression directly

Confidence: high

Scope-risk: narrow

Tested: cargo fmt --all --check

Tested: cargo test -p revelo-parsers-container mkv --all-targets

Not-tested: Real large MKV/WebM media matrix
Add synthetic WAV, RF64 and AIFF fixtures with large metadata and sample chunks, and assert parser byte access remains below the metadata-only budget. RF64 receives minimal ds64 handling so the regression can cover 64-bit data sizes instead of treating the format as unsupported.

Constraint: Audio regressions must be committable without large binary media

Rejected: Leave RF64 as documentation-only coverage | the benchmark contract explicitly includes RF64

Confidence: medium

Scope-risk: moderate

Tested: cargo fmt --all --check

Tested: cargo test -p revelo-parsers-container wav --all-targets

Tested: cargo test -p revelo-parsers-container aiff --all-targets

Not-tested: Real RF64 files larger than 4 GiB
senpai added 7 commits June 28, 2026 04:27
The Criterion harness now has explicit small and large/sparse from_file_mmap slots for WebM, Matroska, RF64, AIFF, FLAC, MP3, Ogg, MPEG-TS and MPEG-PS, in addition to the existing WAV and MP4/MOV/SNV2 cases. The old generic WAV labels are made explicit so future result tables can group formats without guessing.

Constraint: Real benchmark timing runs are intentionally paused until the machine is idle and the user gives the go-ahead.

Rejected: Dashboard-only coverage | it would not exercise the actual Revelo benchmark harness.

Confidence: medium

Scope-risk: moderate

Directive: Do not publish timing numbers for these new cases until cargo bench is run under the official rerun protocol.

Tested: cargo fmt --all --check; cargo test -p revelo --bench parse_benchmark --no-run; git diff --check

Not-tested: cargo bench timing run; real-media benchmark matrix
The extended Criterion matrix now has small sanity rows for MP4 moov-front, MOV moov-tail and SNV2 moov-tail, matching the large sparse rows already present. This keeps the small/large benchmark split explicit for every core format family.

Constraint: Timing runs remain paused until the official benchmark window.

Rejected: Treat WAV small as the generic small baseline | it did not prove the ISO-BMFF parser path.

Confidence: high

Scope-risk: narrow

Directive: Keep small and large ISO-BMFF rows paired when adding future MP4 layout variants.

Tested: cargo fmt --all --check; cargo test -p revelo --bench parse_benchmark --no-run; git diff --check

Not-tested: cargo bench timing run; real-media benchmark matrix
Complete the synthetic BMFF benchmark matrix for MP4, MOV and SNV2 front/tail layouts, and add access-stat regressions for large FLAC padding, capped MP3 ID3v2 tags, and MPEG TS/PS scanner budgets.\n\nConstraint: Real benchmark timing is intentionally deferred until the machine is idle, so this commit only compiles benchmark wiring and asserts deterministic access costs.\nRejected: Treat all MP3 parsing as metadata-only | frame counting remains a semantic scan, but oversized ID3 metadata is now prefix-capped.\nConfidence: high\nScope-risk: moderate\nDirective: Keep large-metadata tests based on access_stats; timing numbers alone are too noisy for regression safety.\nTested: cargo test -p revelo-parsers-audio -p revelo-parsers-container\nTested: cargo test -p revelo --bench parse_benchmark --no-run\nTested: cargo fmt --all --check\nTested: git diff --check\nNot-tested: cargo bench timing rerun; full workspace test suite
Add an Ogg/Vorbis sparse-tail access-stat regression so the extended synthetic benchmark family has a deterministic guard against file-proportional scans.\n\nConstraint: Ogg page payloads are naturally small compared with the 8 MiB budget, so the regression models a valid Ogg prefix followed by a large sparse tail rather than an impossible single huge page.\nRejected: Use Criterion timing as the only guard | timing is noisy and would not prove bounded raw access.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Ogg large-file regressions access-stat based unless a real parser feature explicitly requires scanning later pages.\nTested: cargo test -p revelo-parsers-audio -p revelo-parsers-container\nTested: cargo test -p revelo --bench parse_benchmark --no-run\nTested: cargo fmt --all --check\nTested: git diff --check\nNot-tested: cargo bench timing rerun; real Ogg corpus
Malformed files with sparse bytes after the declared RIFF/RF64/FORM end made WAV and AIFF parsers walk external tails as empty chunks. The parsers now stop at the declared container boundary, using ds64.riffSize for RF64 when present, and regression tests lock trailing-tail behavior.\n\nConstraint: Preserve valid chunk parsing while ignoring bytes outside the declared container.\nRejected: Treat the benchmark fixture as the only bug | real-world files can also carry appended trailing bytes.\nConfidence: high\nScope-risk: narrow\nDirective: Do not switch these loops back to EOF bounds without an access-stat regression.\nTested: cargo test -p revelo-parsers-container; cargo fmt --check; git diff --check\nNot-tested: Full generated/AO benchmark matrix rerun
The CLI was still reading ordinary single-file inputs into Vecs before parsing, so large metadata-only files paid full-copy and memory costs even though the core ReadBackend already supports mmap. The normal path now maps read-only files and keeps full-read behavior for multi-file analysis and mmap fallback.\n\nConstraint: CLI output, stream filtering, verify mode, and multi-file behavior must remain unchanged.\nRejected: Force mmap for multi-file inputs | companion data still needs an owned combined buffer.\nConfidence: high\nScope-risk: moderate\nDirective: Keep full-read behavior as an explicit fallback or multi-file path, not the default single-file path.\nTested: cargo test -p revelo-cli --no-run; cargo build --release -p revelo-cli; focused hyperfine RF64/WAV/AIFF/AIFC/PS5\nNot-tested: Full generated/AO benchmark matrix rerun
The dispatcher no longer races every parser for large mapped inputs. Update the README and dispatcher crate docs so PR reviewers see the same small-input parallel and large-input bounded sequential behavior implemented in the code.

Constraint: Large mapped inputs use a raw-read-capped sequential detection path to avoid speculative full-file reads

Confidence: high

Scope-risk: narrow

Tested: git diff --check -- README.md crates/revelo-dispatcher/README.md

Not-tested: code unchanged
@vbasky

vbasky commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Thank you for this work — it's a substantial contribution, and it addresses the right problem.

Issue #1 wasn't "mmap is missing"; it was that metadata parsing could still scale with payload size after 0.5.0. This branch fixes that properly: bounded reads, measurable access accounting, parser-specific caps, and regressions that catch full-buffer behavior without relying only on wall-clock benchmarks. The synthetic fixture matrix and cross-tool evidence make the case clearly — especially the PS5/SNV2 and real MP4 rows where 0.5.0 was essentially unchanged from 0.4.6.

I also appreciate the discipline in the commits: caps as parser invariants (not runtime knobs), access-stat tests alongside timing evidence, and honest notes about what the full matrix run covered vs. what landed after 157ea51 (CLI mmap, RIFF/FORM boundary fix). That makes this much easier to review and maintain.

Where we are after merge

The PR achieves what I cared about most: Revelo is now ~O(metadata), not ~O(file). On the 542 MiB real MP4 row, ~4200 ms → ~135 ms is a real step change. MediaInfo (~87 ms) and ffprobe (~77 ms) are still ahead on several BMFF rows — and the PR is clear that parity on scaling was the goal, not winning every latency comparison. That's the right framing.

How I plan to close the remaining gap

The ~50 ms delta on real MP4 (and the larger gap on synthetic PS5/SNV2) looks like constant-factor overhead, not unbounded I/O. I'd like to tackle it in small follow-ups, keeping your access-stat regressions as the guardrail:

  1. Single-pass analysis — detect() can run a full parse (e.g. parse_mp4) and callers run the winner again on a fresh FileAnalyze. I plan to add detect_and_parse() and migrate CLI, Metadata, and cdylib so we don't pay for extraction twice.

  2. Magic routing before the 180-parser table — On large files, probe ftyp / EBML / RIFF / etc. and route directly to the container parser, with the full table as fallback for ambiguous cases.

  3. matches() vs parse() split — Cheap magic/structure checks for detection; one full parse after identification.

  4. ParseMode::Fast — Optional fast path that defers parse_tags, AVC SEI sample peek, and some computed fields when callers only need container/codec summary. Full fidelity stays the default.

  5. Hot-path allocation — into_streams() instead of streams().clone(); profile-guided read-path slimming in release builds, with access stats test-only or behind a feature.

I'll measure each step against the same hyperfine rows and keep workspace tests + access-budget regressions green.

A few questions before / as we merge

  1. Benchmark reproducibility — The cross-tool matrix references benchmark-dashboard-data.generated.json, which isn't in the repo. Would you be open to contributing the fixture generator or a short just bench-compare / hyperfine recipe so we can rerun the 16 synthetic + 16 real rows on future releases?

  2. Matrix vs current HEAD — The screenshot/JSON is from 157ea51, while HEAD includes CLI mmap and the RIFF/FORM boundary fix. Did you spot-check whether those later commits change the headline numbers materially, or should we treat the published table as representative of the core fix rather than exact for HEAD?

  3. Detect-then-reparse — Was the detect → re-run-winner split left intentional for this PR (smaller diff / API stability), with single-pass as a deliberate follow-up? That's how I'm reading it, but wanted to confirm rather than assume.

  4. Cap edge cases — Have you seen any oracle/diff cases where a capped metadata field truncates real content we care about for XML parity? If so, which formats? The synthetic access-stat coverage is strong; real-corpus surprises are the main thing I'd want to know about early.

  5. WASM / sequential detect — Any WASM or other non-parallel targets where large-buffer sequential detect was measured? I want to make sure the large-input path is acceptable everywhere we ship, not just desktop CLI.

  6. Follow-up ownership — I'm planning to take the latency follow-ups above unless you'd prefer to keep pushing on a specific area. Happy either way — just didn't want to step on work you already have in mind.

Thanks again for landing the hard part: making large-file behavior correct and provable. The latency chase is a good problem to have on top of that.

@vbasky
vbasky merged commit cf6c907 into vbasky:main Jun 29, 2026
2 checks passed
@Else00

Else00 commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks again for the detailed review and for merging #4.

I opened follow-up PR #5 here:
#5

I re-read your six follow-up questions from #4 and kept this PR focused on
answering them without taking over the larger parser-roadmap work you outlined.

Short version:

  • reproducible benchmark entry points: just bench-compare and
    just bench-evidence;
  • generated large sparse fixtures plus local real-media manifests kept out of
    git;
  • Hyperfine-backed sanitized JSON output and standalone HTML/PNG table
    rendering;
  • dev-only perf_probe for phase timings;
  • JSON oracle diagnostics alongside the existing revelo-diff XML harness;
  • separate WASM probe for parse(Uint8Array);
  • small fixed-overhead fixes found during the investigation, especially
    removing the Unix date +%z subprocess and reducing parser/tag false
    positives.

Latest benchmark evidence:

One clarification about the new table: the Revelo 0.5.1 column measures the
final published v0.5.1 release after #4 merged. The old PNG in #4 was a
mid-review benchmark from the performance branch. The new pipeline also fixes
and normalizes benchmark mechanics that were still evolving then: generated
fixture construction, local SSD copy workflow, table generation, aligned
columns, timing-vs-render commit metadata and one shared Hyperfine manifest for
all compared tools. So the old and new PNGs should not be compared
row-by-row; the fair comparison is inside the new table.

On your specific questions:

  • Benchmark reproducibility: perf: add benchmark tooling and reduce fixed overhead #5 commits the fixture generator, manifest
    examples, Hyperfine runner, just entry points and table renderer. Generated
    media and local manifests remain outside git.
  • Matrix vs current HEAD: full timings were captured on 2777df3; later
    commits only changed tooling/rendering, so the regenerated JSON records both
    repo.commit = 66999f8 and repo.timing_commit = 2777df3.
  • Detect then reparse: still intentionally left out. perf_probe exposes
    the phase cost, but detect_and_parse() is better as a focused follow-up PR.
  • Cap edge cases / oracle parity: perf: add benchmark tooling and reduce fixed overhead #5 adds targeted JSON oracle diagnostics.
    Representative strict-parity mismatches I checked were already present in
    v0.5.0 and current origin/main, so oracle status stays out of the default
    performance table.
  • WASM / sequential detect: perf: add benchmark tooling and reduce fixed overhead #5 includes run_wasm_probe.py; current
    evidence is 88/88 manifest cases parsed successfully.
  • Follow-up ownership: the larger latency items you listed
    (detect_and_parse, magic routing, matches()/parse(), ParseMode::Fast,
    allocation slimming) still look right to me. perf: add benchmark tooling and reduce fixed overhead #5 keeps to benchmark tooling
    plus small investigation fixes.

Also, small non-blocking release-note question: #4 was a fairly large
contribution and v0.5.1 appears to be based on that work. Would you be open to
including me in the v0.5.1 release notes/credits, and also in the next release
notes if #5 is merged?

Finally, I noticed from your public profile/repos that you have several
media-focused Rust projects besides Revelo, including viser, cathar and
sheathe. We are also working on Rust media crates, and this is an area I care
about. If you are open to it, could I reach out by Telegram, Signal, Element, SimpleX or another contact
channel you prefer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants