Skip to content

perf(git): parallelize git segment shell-outs to reduce prompt latency#7422

Open
mroth wants to merge 1 commit intoJanDeDobbeleer:mainfrom
mroth:git-perf
Open

perf(git): parallelize git segment shell-outs to reduce prompt latency#7422
mroth wants to merge 1 commit intoJanDeDobbeleer:mainfrom
mroth:git-perf

Conversation

@mroth
Copy link
Copy Markdown

@mroth mroth commented Mar 25, 2026

Prerequisites

  • I have read and understood the contributing guide.
  • The commit message follows the conventional commits guidelines.
  • Tests for the changes have been added (for bug fixes / features).
  • Docs have been added/updated (for bug fixes / features).

Description

Restructure Enabled() to run independent git commands concurrently instead of sequentially, reducing the critical path from 5-7 serial git invocations down to 2 rounds.

Problem

With all features enabled (fetch_status, fetch_user, fetch_push_status, fetch_upstream_icon), the git segment executes up to 7 sequential git shell-outs:

  1. setUser()git config user.name, git config user.email
  2. setStatus()git status --porcelain=2
  3. setHEADStatus()git rev-parse, git describe (conditional)
  4. setBranchStatus() → (no git calls)
  5. setPushStatus()git rev-list ahead, git rev-list behind
  6. getUpstreamIcon()git remote get-url (fallback only)

Changes

  • Phase 1: setUser() runs concurrently alongside setStatus() via wg.Go
  • Phase 2: After setStatus() completes, setHEADStatus(), setPushStatus(), and getUpstreamIcon() fan out in parallel
  • setPushStatus() internally parallelizes its two rev-list calls
  • setUser() consolidated from 2 git config calls to 1 via --get-regexp

Each goroutine writes to distinct fields on the Git struct — no synchronization beyond WaitGroup is needed. All goroutines are joined before Enabled() returns.

Benchmark results

Measured on Apple M4 Pro / 2TB SSD (median git segment duration, all features enabled):

Scenario Before After Reduction
Small repo (native git) 34ms 19ms 44%
Small repo (50ms synthetic latency) 394ms 156ms 60%
Linux kernel (93k files) 320ms 302ms 6%
Kubernetes (25k files) 303ms 286ms 6%

The improvement is largest when per-call overhead is significant relative to git status time (small/medium repos, slow filesystems). On very large repos where git status dominates, the absolute savings are ~17-20ms.

Testing

  • All existing tests pass (go test "./...")
  • Race detector clean (go test -race)
  • golangci-lint clean

Generated with assistance from Claude Code (claude-opus-4-6), but all code manually reviewed.

The git segment shells out to git multiple times sequentially during
Enabled(). Each invocation pays process-spawn and I/O overhead, making
the total latency additive. With all features enabled, the prior flow
was:

  1. setUser()        → git config user.name, git config user.email
  2. setStatus()      → git status --porcelain=2
  3. setHEADStatus()  → git rev-parse, git describe (conditional)
  4. setBranchStatus() → (no git calls)
  5. setPushStatus()  → git rev-list ahead, git rev-list behind
  6. getUpstreamIcon() → git remote get-url (fallback only)

That's 5-7 sequential git invocations in the common/worst case.

Restructure Enabled() to run independent git commands concurrently,
reducing the critical path to 2 serial rounds:

- Phase 1: setUser() runs concurrently alongside setStatus()
- Phase 2: setHEADStatus(), setPushStatus(), and getUpstreamIcon()
  fan out in parallel after setStatus() completes
- setPushStatus() internally parallelizes its two rev-list calls
- setUser() consolidated from 2 git config calls to 1 via --get-regexp

Each goroutine writes to distinct fields on the Git struct, so no
synchronization beyond WaitGroup is needed. All goroutines are joined
before Enabled() returns.

Measured improvement on Apple M4 Pro / 2TB SSD (median git segment
duration, all features enabled):
- Small repo: 34ms → 19ms (44% faster)
- With 50ms synthetic latency per call: 394ms → 156ms (60% faster)
- Linux kernel (93k files): 320ms → 302ms (6% faster)
- Kubernetes (25k files): 303ms → 286ms (6% faster)

Generated with assistance from Claude Code (claude-opus-4-6), but all
code manually reviewed.
Copilot AI review requested due to automatic review settings March 25, 2026 00:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR reduces git segment prompt latency by restructuring Git.Enabled() to execute independent git shell-outs concurrently instead of serially, and by consolidating user config lookups into a single git invocation.

Changes:

  • Parallelize setUser() with status collection and fan out additional git-dependent work after setStatus().
  • Update setUser() to use git config --get-regexp instead of two separate git config calls.
  • Parallelize the ahead/behind rev-list --count calls inside setPushStatus().

@JanDeDobbeleer
Copy link
Copy Markdown
Owner

Love this, great addition!

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.

3 participants