High Performance Git

Section V · Write Pressure, Diagnosis, and Recovery

Chapter 19

Instrumenting Git

Pencil sketch of a person using a telescope under a bright night sky.

Git's built-in tooling (plus ordinary Unix tools) gives you quite a bit already to understand your repo. You can time commands, capture repository layout, trace nested regions, log network chatter, and keep a small baseline for later comparison.


Start With One Reproducible Command

I start with one command I can run again without changing five other things at the same time — something like git status in the same checkout, git log -- path/to/file, or git fetch origin. Commands that mutate checkout state, like git switch, need a different measurement approach; Chapter 20 covers that case. Run the same command several times before you get fancy:

time git log --oneline -n 20 >/dev/null
time git log --oneline -n 20 >/dev/null
time git log --oneline -n 20 >/dev/null

Decide What "Slow" Means In This Context

There is no universal Git number that separates "fine" from "bad." The useful comparison is usually local: this checkout before and after a change, this repository on two machines, fetch --dry-run versus fetch, or the same command with and without a traceable feature enabled. That still leaves a practical question: what counts as enough to investigate? Under a few hundred milliseconds usually feels instant. One or two seconds is noticeable in an interactive command. Five seconds or more is usually disruptive if the command is part of normal development. And a 2x or 3x regression in the same environment is worth taking seriously even if the absolute number is smaller.

The point is to anchor the discussion in a real workload. git status taking 1.5 seconds in a very large monorepo may be acceptable for now. The same 1.5 seconds in a small repo after a config change is a regression.

Capture Repository Shape Before You Start Guessing

Timing alone is not enough. Before trusting a number, name the repository you're timing. Take a quick snapshot of the repository you are talking about:

git --version
git count-objects -vH
test -f "$(git rev-parse --git-path objects/info/commit-graph)" && echo "commit-graph: yes" || echo "commit-graph: no"
test -f "$(git rev-parse --git-path objects/pack/multi-pack-index)" && echo "midx: yes" || echo "midx: no"
ls "$(git rev-parse --git-path objects/pack)"/*.bitmap 2>/dev/null || true

git count-objects gives you a fast read on loose objects, packed objects, and pack count. That alone can explain more than you might expect. If the question looks storage-heavy, git verify-pack belongs nearby too.

Keep A Small Baseline On Disk

Do not trust yourself to remember what the repository looked like before you changed it. Keep some data around:

mkdir -p perf

git --version >perf/version.txt
git count-objects -vH >perf/count-objects.txt
git config --show-origin --get-regexp '^(core\.fsmonitor|index\.|gc\.|maintenance\.|commitGraph\.|feature\.)' >perf/config.txt || true

Use Trace2 When Timing Stops Being Enough

Git's Trace2 outputs are the first place to reach when wall-clock timing is not enough:

Examples:

GIT_TRACE2_PERF=/tmp/status.perf git status >/dev/null
GIT_TRACE2_EVENT=/tmp/status.json git status >/dev/null

If the command spawns child processes or hides in several layers of internal work, Trace2 breaks down where the time goes.

Use Narrow Traces For Narrow Questions

Sometimes you do not need full Trace2 output. A few useful examples:

GIT_TRACE_PERFORMANCE=1 git status >/dev/null
GIT_TRACE_SETUP=1 git status >/dev/null
GIT_TRACE_REFS=1 git for-each-ref --count=5 >/dev/null
GIT_TRACE_PACKET=/tmp/fetch.packet git fetch origin

Those answer different questions:

Keep Trace Output Separate From Normal Output

Trace output gets messy fast if you mix it with ordinary command output. Write traces to files:

mkdir -p perf

GIT_TRACE2_PERF=perf/fetch.perf git fetch origin >perf/fetch.out 2>perf/fetch.err
GIT_TRACE_PACKET=perf/fetch.packet git fetch origin >/dev/null

Git also redacts some sensitive values by default when tracing is enabled, but trace files can still contain URLs, branch names, path names, and other useful context. Treat them like logs.

Instrument The Network Separately From The Checkout

Remember that clone and fetch often hide several costs under a single command. A slow clone can include server-side negotiation, transfer time, local unpack, and checkout and materialization. Packet tracing and Trace2 let you separate transport cost from local checkout cost before you start changing bundle strategy, bitmaps, or sparse settings.