Performance Architecture & Benchmarks¶
This document outlines the performance characteristics, benchmarking architecture, and baseline
performance metrics for findfmt.
1. Performance Goals & Architecture¶
findfmt is engineered to deliver high-throughput filesystem traversal and intelligent content
classification across large codebases and monorepos (>100,000 files).
The performance model focuses on three primary metrics:
Traversal Throughput (files/sec and dirs/sec): Maximizing file discovery and content inspection throughput across large repository hierarchies while pruning excluded trees early via
.gitignorespecifications.Latency to First Yielded Result (TTFR): Measuring first-byte responsiveness (in milliseconds) from execution start until the first matching item is yielded. Low TTFR is critical for pipe streaming and interactive subshells.
Peak Heap Memory Footprint: Tracking peak heap memory allocation using Python’s
tracemallocstandard library to ensure low, bounded memory usage during multi-level directory traversal.
2. Core Optimization Vectors¶
The traversal and classification pipeline incorporates several targeted optimizations to maximize throughput across large repository hierarchies:
2.1. Redundant Syscalls & Path.resolve() Elimination¶
Earlier implementations resolved relative paths by invoking Path.resolve() on every discovered
file. In deep directory structures, resolving paths generates tens of thousands of redundant
readlink and stat system calls.
findfmt optimizes this by computing directory-relative paths with os.path.relpath() and prefix
operations derived from pre-computed root strings. File classification operates on the entry’s
original path without resolving symlinks for regular files, avoiding per-file link resolution
overhead.
2.2. DirEntry Metadata Preservation & Single-Buffer Content Reads¶
Python’s os.scandir() yields os.DirEntry objects whose cached file type (is_file, is_dir,
is_symlink) and stat() attributes are populated directly from the operating system’s directory
stream.
findfmt preserves and forwards os.DirEntry instances throughout traversal and classification:
Zero-Stat Verification: Avoids secondary
os.stat()syscalls by reusing cached entry metadata.Single-Buffer Content Inspection: When content inspection is required (for shebang or MIME detection),
findfmtreads an initial buffer (the first 2,048 to 4,096 bytes) once into memory. This buffer is shared across shebang parsing, MIME type sniffing, and tag classification, eliminating duplicateopen()andread()syscalls on candidate files.
2.3. Zero-Allocation Gitignore Rule Matching Hot Loop¶
In traversal loops evaluating multiple active .gitignore specifications:
Path operations avoid redundant
Path.is_relative_to()andPath.relative_to()allocations.Relative path representations are passed as normalized strings, leveraging fast string prefix matching to evaluate directory boundaries and pruning rules.
2.4. Fast-Path Filter Short-Circuiting¶
When specific tags are requested (for example, --tag python):
can_skip_file_by_tags()inspects the filename extension before opening the file.If an extension definitively excludes the requested tags and indicates no executable shebang is possible (such as known binary formats or fixed non-script extensions), the file read is bypassed entirely.
3. Parallel Batch Execution & Concurrency Analysis¶
Parallelizing the content-inspection phase using concurrent.futures.ThreadPoolExecutor was
evaluated against the streaming generator architecture on modern multi-core NVMe systems.
3.1. ThreadPoolExecutor vs. Streaming Generator Benchmarks¶
Empirical profiling evaluated a 4-worker ThreadPoolExecutor against the sequential streaming
generator across a test suite of 1,000 files:
Sequential Streaming Generator: Processed 1,000 files in 0.0584 seconds (~17,131 files/second).
ThreadPoolExecutor (4 Workers): Processed 1,000 files in 0.1076 seconds (~9,295 files/second).
Multi-threading introduced an approximate 1.84x slowdown compared to the single-threaded implementation.
3.2. Concurrency Trade-Off Rationale¶
Several architectural factors explain why single-threaded traversal outperforms thread pools for local filesystem inspection in Python:
OS Page Cache & SSD Latency: Reading initial file buffers (2 KB) from modern NVMe storage and operating system page caches executes in microseconds. I/O wait times are negligible compared to thread scheduling costs.
GIL Contention & Dispatch Overhead: Submitting tasks to thread pools, queuing futures, context switching, and Global Interpreter Lock (GIL) contention in CPython introduce synchronization overhead that dwarfs the cost of reading small file headers sequentially.
Time-to-First-Result (TTFR) Degradation: Batch thread dispatching chunks results, delaying initial yields. The single-threaded generator yields matching entries immediately (sub-millisecond TTFR), which is essential for interactive subshells and piping into tools like
fzforhead.Bounded Memory Guarantees: A pure generator maintains constant memory, whereas parallel thread pools accumulate collections of pending futures and buffers.
Based on these empirical results, findfmt retains its single-threaded streaming generator design
as the optimal performance architecture.
4. Automated Benchmark Harness (tools/benchmark.py)¶
The repository includes a dedicated, automated benchmark suite at tools/benchmark.py that
generates reproducible synthetic datasets and measures traversal performance across standard tiers.
4.1. Tiered Datasets¶
The harness supports three standardized dataset tiers alongside custom directory targeting:
Small Tier (<1,000 files): Generates approximately 800 files across nested directories with
.gitignoreexclusion rules. Designed for fast local feedback (<1 second execution).Medium Tier (10,000–50,000 files): Generates approximately 12,000 files across multiple package subtrees, simulating standard enterprise codebases.
Monorepo Tier (>100,000 files): Stress test dataset simulating large monorepo structures with deep hierarchies and mixed file types.
Custom Directory Target (
-d, --target-dir): Benchmarks real-world directory trees directly, such as thefindfmtrepository or user-specified projects.
4.1.1. Deterministic Reproducibility¶
Synthetic trees are generated deterministically using a fixed random seed (--seed 42 by default).
The generator constructs realistic directory trees with diverse file extensions (.py, .sh,
.md, .json, .yaml, .txt, .bin), executable scripts without extensions containing shebang
headers, and root .gitignore exclusion specifications.
4.2. Filesystem Topologies¶
To model realistic disk structures, the benchmark harness supports representative topologies via
--topology (-T):
Standard (
standard): A balanced multi-tier package tree (60 directories) simulating a typical project directory hierarchy.Flat (
flat): A single directory containing all generated files (for example, 10,000 files in one folder), isolating raw directory stream parsing throughput from recursive traversal overhead.Deep (
deep): A deeply nested hierarchy (20 levels of nesting with a branching factor of 2), testing recursion depth limits, call-stack efficiency, and path concatenation overhead.Real-World Monorepo (
real-world): A realistic monorepo topology featuring multiple sub-projects alongside massive ignored directories (node_modules/,.venv/,dist/,build/) containing thousands of excluded files, verifying early.gitignoredirectory pruning efficiency in $O(1)$ time.
4.3. Output Formatters & Classification Modes¶
The harness benchmarks output formatting throughput, streaming efficiency, and Time-to-First-Result
(TTFR) latency via --format (-F) and --list-tags:
Streaming Formats (
text,jsonl,tsv,csv): Streams entries lazily directly to the sink without buffering matching inventories into RAM, maintaining bounded $O(1)$ heap overhead.Buffered Formats (
json,tree,table): Ingests all matching records before building structured trees or serialized tabular representations.Tag & MIME Inspection (
--list-tags): Measures content inspection overhead when resolving MIME classifications and tag metadata across discovered files.
5. Command-Line Interface & Usage¶
5.1. Basic Execution¶
Execute the default benchmark suite on the small tier:
uv run python tools/benchmark.py
5.2. Tier Selection & File Overrides¶
Run specific or all dataset tiers:
# Execute medium tier benchmark
uv run python tools/benchmark.py --tier medium
# Execute all standard tiers (small, medium, monorepo)
uv run python tools/benchmark.py --tier all
# Custom file count override
uv run python tools/benchmark.py --tier small --files 500
5.3. Topology Benchmarking¶
Benchmark specific filesystem topologies:
# Benchmark flat topology with 10,000 files
uv run python tools/benchmark.py --topology flat --files 10000
# Benchmark deeply nested hierarchy (20 levels)
uv run python tools/benchmark.py --topology deep --files 5000
# Benchmark real-world monorepo topology with ignored dependencies
uv run python tools/benchmark.py --topology real-world
# Keep the generated tree on disk for inspection (its path is printed to stderr)
uv run python tools/benchmark.py --topology deep --files 5000 --keep-temp
5.4. Formatter Comparison & Classification¶
Compare streaming vs buffered output formats:
# Compare all output formatters on small tier
uv run python tools/benchmark.py --tier small --format all
# Measure MIME and tag classification overhead
uv run python tools/benchmark.py --tier small --format text --list-tags
5.5. Benchmarking Existing Directories¶
Benchmark an existing repository or directory path:
uv run python tools/benchmark.py --target-dir .
5.6. Multiple Iterations & Warmup¶
Execute multiple iterations with warmup runs to compute averaged metrics:
uv run python tools/benchmark.py --runs 3 --warmup 1
5.7. Baseline Recording & Regression Tracking¶
Save baseline metrics to a JSON report:
uv run python tools/benchmark.py --tier small --save-baseline baseline.json
Compare current performance against a recorded baseline:
uv run python tools/benchmark.py --tier small --baseline baseline.json
Fail execution if throughput degrades beyond an allowable percentage threshold:
uv run python tools/benchmark.py --tier small --baseline baseline.json --threshold 10.0
5.8. Machine-Readable JSON Output¶
Emit JSON directly to stdout or export to a file for CI regression tracking:
# Print structured JSON to stdout
uv run python tools/benchmark.py --json
# Save structured JSON report to file
uv run python tools/benchmark.py --output benchmark_report.json
6. Official v1.0.0 Baseline Performance Metrics¶
The canonical v1.0.0 baselines were recorded on an x86_64 Linux environment (CPython 3.13.15, Linux 6.12+):
6.1. Filesystem Topology Baselines¶
Topology / Benchmark Tier |
Matched Files |
Traversed Directories |
Duration (s) |
Throughput (files/s) |
TTFR Latency (ms) |
Peak Heap (MB) |
|---|---|---|---|---|---|---|
Standard Small (800 files) |
518 |
60 |
0.1785 s |
2,902.4 f/s |
3.87 ms |
0.09 MB |
Standard Medium (12k files) |
7,901 |
60 |
2.4989 s |
3,162.0 f/s |
10.24 ms |
0.71 MB |
Flat (10k files in 1 dir) |
8,261 |
0 |
2.3497 s |
3,522.9 f/s |
348.84 ms |
11.94 MB |
Deep (20 levels, 5k files) |
4,119 |
286 |
3.4016 s |
1,211.8 f/s |
3.68 ms |
0.57 MB |
Real-World Monorepo (Ignored) |
826 |
35 |
0.3734 s |
2,213.1 f/s |
5.93 ms |
0.19 MB |
findfmt Repository |
233 |
1,933 |
0.1575 s |
1,480.3 f/s |
9.24 ms |
0.13 MB |
Note: In the Real-World Monorepo benchmark, 5,000+ files located in node_modules/ and .venv/
were pruned in $O(1)$ time at the directory root level, completing in 0.37 seconds.
6.2. Output Formatter & Classification Performance (Medium Tier, 7,901 Files)¶
Formatter / Mode |
Category |
Duration (s) |
Throughput (files/s) |
TTFR Latency (ms) |
Peak Heap (MB) |
Memory Scaling |
|---|---|---|---|---|---|---|
Raw Traversal |
Engine |
2.5437 s |
3,106.1 f/s |
24.75 ms |
2.89 MB |
$O(1)$ Bounded |
Text ( |
Streaming |
2.6332 s |
3,000.6 f/s |
11.07 ms |
0.72 MB |
$O(1)$ Bounded |
JSONL ( |
Streaming |
3.1472 s |
2,510.4 f/s |
10.43 ms |
0.72 MB |
$O(1)$ Bounded |
TSV ( |
Streaming |
2.5570 s |
3,089.9 f/s |
0.02 ms |
0.72 MB |
$O(1)$ Bounded |
CSV ( |
Streaming |
2.5785 s |
3,064.2 f/s |
0.04 ms |
0.84 MB |
$O(1)$ Bounded |
JSON ( |
Buffered |
2.9981 s |
2,635.3 f/s |
2,997.77 ms |
15.25 MB |
$O(N)$ Linear |
Tree ( |
Buffered |
16.3280 s |
483.9 f/s |
16,265.90 ms |
36.30 MB |
$O(N)$ Linear |
Table ( |
Buffered |
57.7142 s |
136.9 f/s |
57,581.79 ms |
64.80 MB |
$O(N)$ Linear |
Text + Tags |
Classified |
3.5339 s |
2,235.8 f/s |
14.60 ms |
0.72 MB |
$O(1)$ Bounded |
7. Regression Testing in CI¶
The benchmarking harness and automated test suite ensure continuous regression protection:
Canonical v1.0.0 baselines are committed and versioned at
tests/fixtures/v1_baseline.json.Automated integration tests in
tests/integration/test_performance_baselines.pycontinuously assert that:Streaming formatters (
text,jsonl,tsv,csv) maintain bounded $O(1)$ heap memory.Early
.gitignorepruning bounds traversal time and memory when skipping massive directory trees.Continuous multi-run traversals exhibit zero unbounded memory leaks.
Pull requests can execute regression checks via:
uv run python tools/benchmark.py --baseline tests/fixtures/v1_baseline.json --threshold 10.0
If throughput degrades by more than the specified threshold percentage, the tool emits a comparison
table and exits with non-zero status code 1, halting the quality gate.