Skip to content

perf(gix-status): cap automatic macOS modification checks at three threads (#3185) - #3187

Merged
Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
trevorlasn:status-macos-thread-cap
Nov 10, 2026
Merged

Sebastian Thiel (Byron) merged 2 commits into
GitoxideLabs:mainfrom
trevorlasn:status-macos-thread-cap

Conversation

@trevorlasn

Copy link
Copy Markdown
Contributor

Written by Claude Code, an AI agent, for trevorlasn.

Fixes #3185

Motivation

We were evaluating gix as the status backend of residue, which runs one status per Git worktree to decide whether it can be removed. On macOS, Repository::status() started one modification-check thread per logical core (14 here), while gix status used 3. The extra threads cost CPU without finishing sooner on a clean worktree.

What changed

  • gix_status::index_as_worktree::Options resolves thread_limit None and Some(0) to at most 3 threads on macOS. This follows the macOS caps in gix-worktree-state checkout and gix-worktree removal.
  • Positive limits still override. Other platforms are unchanged.
  • Repository::status(), index_worktree_status(), is_dirty() and direct gix-status callers all get the cap.
  • The CLI drops its own cfg!(target_os = "macos").then_some(3). gix status still uses 3 threads. Only --threads 0 changes, from all cores to the cap.
  • Docs: the thread_limit docs in gix-status and gix, and the --threads help.
  • New unit test index_as_worktree::types::tests::thread_limit_resolution.

Before and after

Apple M4 Max, 14 cores, macOS 26.3.1, APFS, warm cache. A release-built probe calls Repository::status() with defaults and collects every item. Before is main at e2743fe28, after is this branch. /usr/bin/time -l, before and after alternate each round. The worktree is a git clone --shared of a repository with 60,038 index entries and 9 status items. Other work shared the machine: load average 33 to 43, so wall times are noisy. Medians of two batches:

Worktree Rounds Wall s CPU s (user + sys) Peak footprint MB
Clean stat data 21, 21 0.19 → 0.18, 0.13 → 0.10 0.59 → 0.39, 0.57 → 0.30 29.5 → 28.8, 29.5 → 28.5
All 60,033 files re-read (1.21 GB) 9, 11 1.71 → 1.90, 1.78 → 2.08 11.38 → 4.64, 12.66 → 4.36 36.7 → 33.6, 37.1 → 35.1
  • The cap used less CPU in 39 of 42 clean pairs and in all 20 re-read pairs.
  • Status output was byte-identical across all runs.
  • A peak of 20 process threads before and 9 after was sampled with ps -M during a re-read run.

The second row is the case the old CLI comment named: "MacOS also scales if reading a lot of files for refresh index". Every tracked file was touched, so each status hashes all files again until something writes the index back. The cap costs wall time there. A sweep with explicit thread_limit on that worktree (9 rotated rounds, load 17 to 60) shows the direction:

Threads 1 2 3 4 6 8 10 14
Wall s 10.26 6.02 4.05 3.19 2.18 1.80 1.84 1.75
CPU s 4.10 3.53 3.67 4.14 5.78 7.56 6.85 9.71

Its 3-thread wall time is about twice the batch figure. With 18 variants per round, load changed more within a round. If the re-read case matters more than idle CPU, 8 threads came within 3% of 14 on wall time with 22% less CPU in this sweep. The cap value is one constant.

Checks run

All with Rust 1.99.1 unless noted.

  • cargo fmt --all -- --check: clean (also on 1.94.0). Nightly rustfmt with rustfmt-nightly.toml adds no new diffs in the touched files.
  • cargo clippy --locked --all-targets -p gix-status -p gix -p gitoxide -- -D warnings -A unknown-lints --no-deps: clean.
  • Same clippy for --target x86_64-unknown-linux-gnu -p gix-status -p gix: clean.
  • cargo clippy -p gix-status --no-default-features --features sha1 (no parallel): clean.
  • cargo check for gix-status, gix, gitoxide: clean. Linux target: gix-status, gix, and gitoxide with --no-default-features --features small.
  • GIX_TEST_IGNORE_ARCHIVES=1 cargo test -p gix-status: 1 unit and 42 integration tests pass.
  • The new test fails with left: None, right: Some(3) when the cap is removed.
  • GIX_TEST_IGNORE_ARCHIVES=1 cargo test -p gix: all pass (597 in tests/gix, 46 doctests, the rest 44).
  • cargo test -p gitoxide: 7 pass.
  • tests/journey.sh with the max,http-client-curl-rustls build: exit 0, including "keeps status worker spans under the command's root span".
  • cargo doc --no-deps -p gix-status -p gix: no new warnings.

Not verified

  • An idle machine, a cold cache, Intel Macs, or other core counts.
  • is_dirty() timing on its own.
  • Linux and Windows at runtime. The change is cfg(target_os = "macos") only.
  • CI, and just test (just is not installed here).
  • The fixture archives that GIX_TEST_IGNORE_ARCHIVES=1 regenerated were reverted, not committed.

🤖 Generated with Claude Code

…reads (GitoxideLabs#3185)

On macOS, `gix status` limits the tracked-file modification check to three
threads, but the cap lived in the CLI (`src/plumbing/main.rs`). Library
callers, including `Repository::status()`, `Repository::index_worktree_status()`
and `Repository::is_dirty()`, used one thread per logical core. On a 14-core
Mac with clean stat data, the extra threads added CPU time without finishing
sooner. This came up while evaluating `gix` for the status call of
[residue](https://github.com/whiteheap/residue-cli), which runs one status per
Git worktree it checks.

Resolve `None` and `Some(0)` to at most three available logical cores in
`gix_status::index_as_worktree::Options`, so every caller inherits the cap, as
checkout and worktree removal already do with theirs. Positive limits remain
overrides, and other platforms keep their defaults. The CLI drops its special
case and still uses three threads; only `--threads 0` changes, from all cores
to the automatic cap. Update the option docs and the `--threads` help.

## Measurements

Apple M4 Max, 14 logical cores, 36 GB, macOS 26.3.1, APFS, warm cache. A
probe built in release mode with Rust 1.94.0 calls `Repository::status()` with
its defaults and collects every item. It is built against `main` at
`e2743fe28` (before) and against this commit (after), runs under
`/usr/bin/time -l`, and alternates before and after each round. The worktree
is a `git clone --shared` of a repository with 60,038 index entries and 9
status items. Other work shared the machine, with a 1-minute load average of
33 to 43, so wall times are noisy and CPU times are steadier. Medians of two
batches each:

| Worktree | Wall s | CPU s (user + sys) | Peak footprint MB |
|---|---|---|---|
| Clean stat data, 21 + 21 rounds | 0.19 → 0.18, 0.13 → 0.10 | 0.59 → 0.39, 0.57 → 0.30 | 29.5 → 28.8, 29.5 → 28.5 |
| All 60,033 files re-read (1.21 GB), 9 + 11 rounds | 1.71 → 1.90, 1.78 → 2.08 | 11.38 → 4.64, 12.66 → 4.36 | 36.7 → 33.6, 37.1 → 35.1 |

The cap used less CPU in 39 of 42 clean pairs and in all 20 re-read pairs.
The status output was byte-identical in every run.

The second row is the case the removed CLI comment noted: "MacOS also scales
if reading a lot of files for refresh index". Touching every tracked file
makes each status hash all files again, as long as nothing writes the index
back. The cap costs wall time there. A sweep with explicit limits on that
worktree, 9 rotated rounds under a load of 17 to 60, shows the direction:

| Threads | 1 | 2 | 3 | 4 | 6 | 8 | 10 | 14 |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| Wall s | 10.26 | 6.02 | 4.05 | 3.19 | 2.18 | 1.80 | 1.84 | 1.75 |
| CPU s | 4.10 | 3.53 | 3.67 | 4.14 | 5.78 | 7.56 | 6.85 | 9.71 |

The sweep's 3-thread wall time is about twice the batches' figure: with 18
variants per round, the load changed more within a round. Callers that expect
many reads can pass a higher `thread_limit`.

## Limits

One Mac, warm cache, heavy background load. Not measured: an idle machine, a
cold cache, Intel Macs or other core counts, and `is_dirty()` on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-09T18:37:41.576822Z eaed082 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues remain for the change centralizing macOS status thread limits in gix-status.

No open issues 🎉

What changed in this PR

Moves the macOS status thread cap from the CLI into gix-status, giving library and CLI callers consistent automatic limits and addressing #3185.

Changes:

  • Caps automatic tracked-file modification checks at three threads on macOS, while preserving positive overrides and other platforms’ behavior.
  • Removes the CLI-specific cap and updates API documentation and --threads help.
  • Adds a unit test covering automatic limits and explicit overrides.
File Description
src/​plumbing/​options/​mod.rs Clarifies automatic thread selection in CLI help.
src/​plumbing/​main.rs Removes the CLI-specific macOS status cap.
gix/​src/​status/​index_worktree.rs Documents the shared cap and explicit overrides.
gix-status/​src/​index_as_worktree/​types.rs Implements and tests platform-specific thread-limit resolution.
gix-status/​src/​index_as_worktree/​function.rs Uses the resolved limit when sizing modification-check workers.

Review effort: Balanced

- Make the thread cap discoverable by officially overriding the options default.
  That way, users can still override it with a concrete value and have it work as advertised.
@Byron
Sebastian Thiel (Byron) merged commit 8118e5e into GitoxideLabs:main Nov 10, 2026
24 of 33 checks passed
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.

macOS: Repository::status() uses one thread per core for the modification check, while gix status caps macOS at 3

3 participants