# Performance Plan Imported on: 2026-04-30 Fully generated as overview, intiial motivation is to keep track of `gix-index` performance and possible improvements. Source basis: - All `TODO(perf)` markers in this checkout. - The two `TODO(performance)` markers, as they are equivalent in intent. - Nearby generic TODOs that may affect common Git performance paths. - A local comparison against `/Users/byron/dev/github.com/git/git`. Working assumption: priorities are based on ordinary interactive and networked Git use, not on synthetic microbenchmarks alone. ## Typical Workflows These workflows define the impact ordering: - `git status`, `git add`, and editor integrations that repeatedly refresh the index, classify the worktree, and scan untracked paths. - `git commit`, `git checkout`, `git switch`, `git merge`, and `git reset --hard`, which depend on fast index reads, tree/index conversion, checkout, and content merge. - `git diff`, `git log -- `, and `git blame`, which traverse trees, objects, and diffs repeatedly. - `git fetch`, `git pull`, and `git clone`, which stress negotiation, object lookup, pack ingestion, duplicate avoidance, and pack/index refresh. - `git push`, `git gc`, `git repack`, and object verification, which stress pack traversal, delta reconstruction, bitmaps, and multi-pack metadata. - `git archive` and export-style commands, which stream trees with attributes and filters. ## Priority Backlog ### P0: Status, Add, And Index Refresh - [ ] **index-lookup-accelerator**: Make index path lookup acceleration a default fast path, not only an ignore-case helper. - Source TODOs: - `gix-dir/src/walk/classify.rs:396`: build a multi-threaded hash table so lookups are always accelerated, even for case-sensitive paths. - `crate-status.md:844`: always use multi-threaded initialization of the case-insensitive hash table to accelerate index lookups. - `gix-index/src/access/mod.rs:144`: multi-threaded insertion needs a raw table with multiple bucket locks. - Workflow impact: very high for `status`, `add`, checkout collision checks, untracked filtering, and pathspec-heavy commands in large worktrees. - Git comparison: Git's `name-hash.c` keeps `index_state.name_hash` and `dir_hash` for index lookups. It uses lazy initialization, a per-thread threshold (`LAZY_THREAD_COST`), bucket-derived locks, a two-phase directory/name hash build, and exact-then-ignore-case comparison. Git's `read-cache.c` wires name-hash updates into index entry insertion. - Plan: - Factor `gix-index::AccelerateLookup` into a general name/directory lookup accelerator usable for case-sensitive and case-insensitive callers. - Add a parallel build path with sharded insertion or per-thread partial tables merged at the end. - Benchmark `status` and `dirwalk` on large repos with `core.ignoreCase` true and false. - [ ] **status-refresh-heuristics**: Close the status refresh heuristic gap. - Source TODOs: - `gix-status/src/index_as_worktree/function.rs:144`: decide when parallelization is not worth it; Git uses about 500 entries per thread, capped at 20 preload threads. - `src/plumbing/main.rs:433`: make thread-limit tuning configurable; macOS and Linux scale differently. - `gix/src/status/mod.rs:165`: make `Repository::is_dirty()` a dedicated early-stop implementation with parallelism. - Workflow impact: very high for prompts, IDEs, `git status`, and dirty checks in automation. - Git comparison: Git's `preload-index.c` uses `MAX_PARALLEL = 20`, `THREAD_COST = 500`, skips staged/submodule/up-to-date/skip-worktree/fsmonitor-valid entries, and marks entries up to date after `lstat`. - Plan: - Measure the current chunk heuristic against Git-like thresholds on Linux and macOS. - Add a dedicated `is_dirty()` path that stops after the first tree/index or index/worktree change. - Make thread policy configurable through repository/config options rather than command-specific constants. - [ ] **untracked-fsmonitor-cache**: Use untracked-cache and fsmonitor data aggressively where valid. - Source TODOs: - `crate-status.md:846`: accelerated walk with the `UNTR` extension. - `gix-index/src/extension/untracked_cache.rs:29` and `:33`: understand directory stat data and extension semantics fully. - `gix-status/src/index_as_worktree/traits.rs:105`: make streaming I/O interruptible. - Workflow impact: very high for `status` in large repositories with many ignored/untracked files. - Git comparison: Git's `wt-status.c` passes `istate->untracked` into `fill_directory()` and avoids full scans when valid; `read-cache.c` also tweaks fsmonitor and untracked-cache state after index load. - Plan: - Finish documenting and validating `UNTR` semantics. - Integrate valid untracked-cache state into `gix-dir` walks and status collection. - Preserve interruptibility for editor integrations and long-running worktree scans. ### P0: Index Read, Decode, And Tree-To-Index - [ ] **index-decode-storage**: Fix index decode storage costs before adding more threads. - Source TODOs: - `gix-index/src/decode/entries.rs:185`: `path_backing.extend_from_slice()` causes large `memmove` time despite apparent capacity. - `crate-status.md:856`: threaded index read spends most time storing paths and currently has little benefit. - `gix-index/src/decode/entries.rs:118`: entries behave like an intrusive path-keyed collection; this likely affects ignore-case and lookup. - `gix-index/src/entry/flags.rs:11`: use persisted path length to save in-memory entry size. - Workflow impact: very high for every command that opens a large repository. - Git comparison: Git's `read-cache.c` memory-maps the index, allocates cache entries from mempools, uses the EOIE and IEOT extensions for threaded extension and entry loading, and stores path bytes inline in `struct cache_entry` (`name[FLEX_ARRAY]`). It also records an index entry offset table when threaded reads are requested. - Plan: - Profile the `path_backing` growth path and replace it with either pre-sized storage, chunked storage, or per-thread path arenas merged without repeated moves. - Evaluate an entry representation that stores path length compactly and gives lookup structures stable references. - Treat parallel decode as second-stage work after path storage stops dominating. - [ ] **tree-index-sort**: Avoid post-sort work when initializing an index from a tree. - Source TODO: - `gix-index/src/init.rs:107`: `remove_file_directory_conflicts()` sorts only to protect against invalid trees; typical valid trees already compare in order. - Workflow impact: high for checkout, archive setup, worktree stream, and operations that synthesize indexes from trees. - Git comparison: Git's cache-tree machinery (`cache-tree.c`) validates tree/index order and can prime an index's cache tree directly from a tree. `read-cache.c` and `cache-tree.c` keep sorted index invariants central. - Plan: - Insert entries in sorted order when walking trees and detect invalid file/directory conflicts during insertion. - Add a fast path for already sorted valid trees and a fall-back verifier for malformed data. - Reuse or extend cache-tree information when constructing an index from `HEAD`. - [ ] **worktree-stream-traversal**: Reduce double traversal in worktree streaming and archive creation. - Source TODOs: - `gix/src/repository/worktree.rs:88`: use the index at `HEAD` if possible. - `gix/src/repository/worktree.rs:89`: non-HEAD trees are effectively traversed twice; object-cache sharing across copied ODB handles is not trivial. - Workflow impact: medium to high for `archive`, export, and tooling that streams a tree with attributes. - Git comparison: Git relies heavily on cache-tree and index state to avoid redundant tree work when the requested tree matches index/HEAD state. - Plan: - Fast-path `HEAD` through an existing index plus cache-tree where available. - For non-HEAD trees, evaluate an explicit shared object/header cache for the two traversal consumers. ### P0: Object Database And Pack Access - [ ] **odb-refresh-wait**: Replace spin/yield waiting during dynamic pack-index refresh. - Source TODO: - `gix-odb/src/store_impls/dynamic/load_index.rs:152`: a potentially hot loop should probably be a condition variable. - Workflow impact: high for fetch, clone, object lookup under concurrency, and commands that observe newly written packs. - Git comparison: Git's pack store preparation is centralized in `packfile.c`, and pack loading/re-preparation is explicit. It avoids leaving multiple callers in a spin loop while another caller is loading the same pack metadata. - Plan: - Replace the yield loop with a wait/notify primitive around index-load state transitions. - Measure concurrent object lookup during fetch and after pack creation. - [ ] **loose-object-single-lookup**: Avoid duplicate loose-object lookup work. - Source TODOs: - `gix-odb/src/store_impls/dynamic/find.rs:292`: remove loose DB `contains()` plus `try_find()` double lookup. - `gix-odb/src/store_impls/dynamic/header.rs:166`: same double lookup for headers. - Workflow impact: medium to high for loose-heavy repositories, freshly created objects, and test fixtures. - Git comparison: Git's ODB lookup API returns object info/content from one lookup path and then moves packs toward the front after hits. - Plan: - Add a single-call loose-object API that can report missing, header-only, or full object data. - Preserve the current borrow-safety shape by returning owned metadata where needed. - [ ] **pack-delta-base-cache**: Revisit pack delta reconstruction and base caching. - Source TODO: - `gix-pack/src/data/file/decode/entry.rs:381`: optimize memory-intensive delta-chain reconstruction after more tests exist. - Workflow impact: high for checkout after fetch, clone verification, diff/log over packed history, and blame. - Git comparison: Git's `packfile.c` uses a `delta_base_cache` keyed by pack and base offset with an LRU size limit, a small preallocated delta stack, and delayed insertion into the cache to avoid races while unpacking. - Plan: - Add coverage for long and shared delta chains first. - Compare current buffer swapping with Git's base-cache strategy and adopt an LRU base cache if repeated base materialization is visible in profiles. - [ ] **fetch-duplicate-objects**: Investigate duplicate received objects during fetch. - Source TODO: - `gix/tests/gix/remote/fetch.rs:128`: tests observe substantial duplication when receiving objects. - Workflow impact: high for fetch, pull, and clone. - Git comparison: Git uses bitmaps, multi-pack-index, pack reuse, and object-entry de-duplication in pack-object paths. - Plan: - Determine if duplication is transport negotiation, pack ingestion, replacement refs, or iteration/reporting. - Add a counter-based regression test around unique received object IDs and pack refresh count. ### P1: Diff, Blame, Merge, And History Queries - [ ] **blame-rename-diff-cache**: Avoid duplicated tree diff work in blame rename detection. - Source TODO: - `gix-blame/src/file/function.rs:582`: rename tracking repeats tree diff work after the no-rewrite pass. - Workflow impact: medium to high for `git blame`, especially with `-M`, `-C`, and path history over rename-heavy repositories. - Git comparison: Git's `blame.c` first looks for unchanged origins, then renames, then optionally moves/copies. It carries `blame_origin`, `blame_entry`, score thresholds, and optional blame Bloom data rather than blindly repeating the same tree walk for every case. - Plan: - Feed the first path-limited tree diff into a rewrite tracker when an addition or deletion is found. - Cache parent/current tree IDs and blob headers per commit pair. - Evaluate a blame-specific origin cache modeled after Git's two-pass origin/rename structure. - [ ] **merge-conflict-structures**: Audit merge tree conflict data structures. - Source TODO: - `gix-merge/src/tree/mod.rs:376`: a better data structure may be needed for some directory/file conflict representation. - Workflow impact: medium for merges and rebases in large trees. - Git comparison: Git's merge and rename machinery keeps maps for directory rename counts and per-path pairing in `diffcore-rename.c`. - Plan: - Profile rename and directory/file conflict heavy merges. - Replace ad hoc per-conflict representation only if lookup or rewrite costs dominate. - [ ] **diff-preprocess-rescans**: Remove avoidable rescans in diff preprocessing. - Source TODO: - `gix-imara-diff/src/myers/preprocess.rs:132`: do not unnecessarily rescan lines. - Workflow impact: medium for large file diffs and blame. - Git comparison: Git's xdiff stack has dedicated preprocessing and histogram/patience variants. `gix-imara-diff` already carries Git-inspired histogram behavior, so this should remain benchmark-led. - Plan: - Add benchmarks for common source-file diffs and large generated files. - Cache sliding-window counts or use prefix/suffix pruning data already computed during tokenization. ### P1: Repository Discovery, Refs, And Path Handling - [ ] **packed-ref-lookup**: Implement packed-buffer aware reference lookup in general handles. - Source TODO: - `gix-ref/src/store/general/handle/find.rs:28`: implement lookup with packed-buffer handling. - Workflow impact: medium for commands that resolve many refs, such as branch listing, fetch negotiation, and revision parsing. - Git comparison: Git's ref backend keeps packed refs as a searchable table and layers loose refs on top. - Plan: - Add a packed-ref buffer search path with loose-over-packed precedence. - Benchmark ref-heavy repos with many tags. - [ ] **literal-path-normalization**: Avoid path normalization through pattern machinery for single literal paths. - Source TODOs: - `gitoxide-core/src/repository/blame.rs:29`, `gitoxide-core/src/repository/merge/file.rs:37`, `tests/it/src/commands/blame_copy_royal.rs:58`: normalize paths without going through patterns. - Workflow impact: medium for porcelain commands over individual paths. - Plan: - Add a literal path normalization helper below pathspec parsing. - Keep pathspec matching for real pattern inputs only. ### P2: Allocation And Micro-Hotspots - [ ] **hex-prefix-stack-decode**: Remove heap allocation from odd-length hex prefix parsing. - Source TODO: - `gix-hash/src/prefix.rs:120`: decode odd hex prefixes without heap allocation. - Workflow impact: low to medium, but may matter for abbreviated object lookup and ref/path disambiguation. - Git comparison: Git's object abbreviation logic in `object-name.c` and `packfile.c` works against fixed-size object IDs and pack fanout searches rather than allocating per prefix. - Plan: - Decode into a stack buffer sized by `Kind::longest()` and copy directly into `ObjectId`. - Add a microbenchmark for odd and even prefix parsing. - [ ] **small-attribute-values**: Use a small byte-string representation for attribute values. - Source TODO: - `gix-attributes/src/state.rs:8`: a small byte string could provide an estimated 5 percent improvement. - Workflow impact: low to medium for status, archive, checkout, and filter-heavy repos. - Plan: - Prototype a `smallvec`-backed byte string with display/serde wrappers. - Validate JSON behavior and attribute query benchmarks before adopting. - [ ] **signed-data-reader**: Stream signed commit data without allocation. - Source TODO: - `gix-object/src/commit/mod.rs:33`: implement `std::io::Read` for `SignedData`. - Workflow impact: low to medium, mostly signature verification and tooling over many signed commits. - Plan: - Add a segmented reader over the two non-signature ranges instead of building a `BString`. - [ ] **packetline-borrow-decode**: Remove extra packet-line decoding caused by borrow-checker workarounds. - Source TODO: - `gix-packetline/src/blocking_io/read.rs:110`: avoid additional decoding of the internal buffer. - Workflow impact: low to medium for fetch/clone protocol handling. - Plan: - Revisit API lifetimes and return owned or borrowed line data explicitly. - [ ] **tempfile-registry-map**: Re-evaluate tempfile registry maps. - Source TODO: - `gix-tempfile/src/lib.rs:75`: use a `gix-hashtable` slot-map once available. - Workflow impact: low for typical workflows, higher for checkout/merge paths that create many temporary files. - Plan: - Benchmark the existing mutexed map against a slot-map and the `hp-hashmap` feature. - [ ] **git-date-token-parser**: Replace brute-force Git date parsing. - Source TODO: - `gix-date/src/parse/git.rs:11`: learn from Git's parser instead of generated brute-force parsing. - Workflow impact: low for normal operations, medium for log/rev parsing over many date filters. - Git comparison: Git's `date.c` uses a token scanner (`parse_date_basic`) with alpha, digit, and timezone matchers, then falls back to approxidate. - Plan: - Replace format-chain parsing with a token scanner for Git-specific formats. - Keep fixtures generated from Git for compatibility. ## Git-Inspired Candidates Without Current TODOs - [ ] **sparse-index-parity**: Sparse-index parity for worktree-heavy commands. - Git comparison: Git's `sparse-index.c` can collapse full index ranges into sparse directory entries and expand on demand. - Candidate impact: very high for monorepos and sparse checkouts. - Next step: audit which `gix-status`, `gix-dir`, checkout, and merge code paths force full-index behavior. - [ ] **commit-graph-bloom**: Commit-graph generation and Bloom-filter use in history walks. - Git comparison: Git's `revision.c` uses commit-graph generation numbers to bound traversal, and blame can initialize Bloom data. - Candidate impact: high for `log -- `, merge-base, blame, and negotiation. - Next step: verify all revision walkers prefer generation numbers and changed-path filters when available. - [ ] **midx-revindex-bitmaps**: Multi-pack-index reverse-index and bitmap-driven object enumeration. - Git comparison: Git's `midx.c`, `pack-revindex.c`, and `packfile.c` use MIDX lookup, reverse-index chunks, and prefixed-object iteration across MIDX before falling back to individual packs. - Candidate impact: high for fetch, clone, gc, prefix lookup, and object iteration in repos with many packs. - Next step: compare `gix-pack` and `gix-odb` MIDX support against Git's lookup order and reverse-index cache behavior. - [ ] **pack-reuse-delta-policy**: Pack reuse and path-based delta compression policies. - Git comparison: Git's `builtin/pack-objects.c` uses pack reuse, bitmap reuse, delta islands, path-based regions, threaded delta search, and delta-cache limits. - Candidate impact: high for push, fetch serving, gc, and repack. - Next step: treat this as a separate pack-generation roadmap after read-side pack performance is measured. ## Measurement Plan - Use Git's `GIT_TRACE_PERFORMANCE=true` and `GIT_TRACE2_PERF=1` baselines on the same repositories as `gix` measurements. - Maintain at least these benchmark repositories: small everyday repo, `git/git`, Linux-sized history, a many-untracked-files worktree, a sparse checkout, and a many-pack repository. - Track wall time, syscalls/stat count, object reads, pack index refreshes, peak RSS, and allocation counts. - For each P0 item, require one benchmark that represents an interactive command and one regression test that protects the intended fast path. ## Immediate Next Moves - [ ] **profile-status-worktrees**: Profile `gix status` on large case-sensitive and case-insensitive worktrees and compare against Git's name-hash/preload behavior. - [ ] **profile-index-decode**: Profile index decode to identify why `path_backing` triggers `memmove`, then choose arena/chunked storage before expanding threaded decode. - [ ] **implement-odb-wait-notify**: Replace the dynamic ODB pack-index load wait loop with wait/notify. - [ ] **fetch-dup-counter**: Add a fetch duplicate-object diagnostic counter before changing negotiation or pack ingestion. - [ ] **prototype-prefix-stack**: Prototype stack-only odd-prefix parsing as a low-risk starter change. ## Exit Criteria - [ ] **status-scale-exit**: `status` and dirty checks scale with index size and untracked-file count within an agreed multiplier of Git on the benchmark corpus. - [ ] **index-throughput-exit**: Index read throughput improves on large indexes without increasing peak memory disproportionately. - [ ] **object-lookup-wait-exit**: Concurrent object lookup during fetch/clone no longer spins while another thread loads pack metadata. - [ ] **pack-benchmark-coverage-exit**: Object lookup and pack delta reconstruction have benchmark coverage before cache/data-structure rewrites land. - [ ] **todo-triage-exit**: The remaining performance TODOs are either implemented, benchmarked and rejected, or converted into narrower tracked tasks.