Performance data

Homebrew core

926,163 commits, 9,262 tracked files and 15 refs at b61ec9f1460b, with a commit-graph file, recorded . 30 samples of each command, the app and Git taking turns.

Milliseconds. App ÷ Git compares the medians.
CommandApp medianApp 95thGit medianGit 95thApp ÷ GitBudget
Working-tree status24.626.824.125.11.03×1.50×
History, first 250 commits485.4495.5453.2460.61.07×3.00×
History, first 500 commits493.2511.4455.5464.51.08×3.00×
Branches with ahead and behind5.87.35.47.11.07×2.00×
Every file in the project22.623.919.7211.14×2.00×
Worktrees with their status22.925.621.723.81.06×1.50×
Worktrees, 1-worktree fixture12.114.111.714.41.02×1.50×
Worktrees, 8-worktree fixture25.228.323.926.51.05×1.50×
Open one commit19.823.618.9211.04×none

Linux kernel, as cloned

1,505,332 commits, 97,359 tracked files and 955 refs at 05441d697f7f, without a commit-graph file, recorded . 30 samples of each command, the app and Git taking turns.

Milliseconds. App ÷ Git compares the medians.
CommandApp medianApp 95thGit medianGit 95thApp ÷ GitBudget
Working-tree status259.8285.32582851.01×1.50×
History, first 250 commits7,871.68,300.67,246.57,634.31.09×3.00×
History, first 500 commits7,874.88,288.47,243.77,688.61.09×3.00×
Branches with ahead and behind14.315.713.714.61.04×2.00×
Every file in the project257.1277.9244.6275.51.05×2.00×
Worktrees with their status7,140.57,502.47,137.77,536.11.00×1.50×
Worktrees, 1-worktree fixture27.830.327.2281.02×1.50×
Worktrees, 8-worktree fixture45.45044.352.61.03×1.50×
Open one commit6,947.67,317.46,960.87,333.21.00×none

Linux kernel, with a commit-graph

1,505,332 commits, 97,359 tracked files and 955 refs at 05441d697f7f, with a commit-graph file, recorded . 30 samples of each command, the app and Git taking turns.

Milliseconds. App ÷ Git compares the medians.
CommandApp medianApp 95thGit medianGit 95thApp ÷ GitBudget
Working-tree status255.4281.6254.5277.91.00×1.50×
History, first 250 commits940966.2787.4812.81.19×3.00×
History, first 500 commits944971.9789.6816.11.20×3.00×
Branches with ahead and behind12.613.112.112.71.04×2.00×
Every file in the project256.3281.7243.7252.31.05×2.00×
Worktrees with their status209.1241.8208228.31.01×1.50×
Worktrees, 1-worktree fixture27.428.727.328.51.00×1.50×
Worktrees, 8-worktree fixture46.149.545.149.61.03×1.50×
Open one commit35.937.634.736.11.03×none

BranchHelm

8,821 commits, 22,410 tracked files and 24 refs, with a commit-graph file, recorded . 30 samples of each command, the app and Git taking turns.

Milliseconds. App ÷ Git compares the medians.
CommandApp medianApp 95thGit medianGit 95thApp ÷ GitBudget
Working-tree status65.968.764.469.11.02×1.50×
History, first 250 commits32.436.512.915.82.48×3.00×
History, first 500 commits35.837.815.618.12.28×3.00×
Branches with ahead and behind5.97.35.87.71.02×2.00×
Every file in the project56.65847.849.41.18×2.00×
Worktrees with their status48.851.647.952.21.02×1.50×
Worktrees, 1-worktree fixture1214.111.313.91.07×1.50×
Worktrees, 8-worktree fixture24.728.824.228.21.02×1.50×
Open one commit25.429.724.527.71.03×none

A fix these measurements found

To draw one page of history, the app read the trunk's entire first-parent chain to learn which rows were on it. The fix reads only as deep as the page reaches and proves the rest with Git's own reachability check. Two builds of the same base, before and after it, timed back to back in one quiet window on the 926,163-commit repository on :

First 250 commits · milliseconds
BuildApp medianGit medianApp ÷ Git
Before930.8347.92.68×
After442.93461.28×

On a short trunk one whole read is quicker than several bounded ones, so the app now runs both and takes the first answer. Every sitting above was taken on a build that has the fix.

How it was measured

A commit-graph file is the index Git writes during its routine maintenance; without one Git must read every commit to order the history. A fresh clone has none, so the Linux kernel is timed both ways.

The release build of the app runs headless in a throwaway profile, with Workspace Trust granted to the repository so that each Git read is the one a person would trigger. A script sends each command over the app's local connection and waits for the whole reply. Beside each command, the same script runs the Git reads that command needs, directly, and the two take turns. So the ratio is everything the app does on top of those reads: starting each one, reading its output, any reads of its own and answering in JSON. A row's budget is the largest ratio the harness accepts for it, and a row over it is marked. Where Git itself answers in a few milliseconds, that fixed cost shows as a larger ratio, and history on BranchHelm's own repository is the clearest case: it starts several Git commands in turn, each with a start-up cost of its own.

  • History also reads the trunk's first-parent chain to mark which rows are on it; Git alone does not, so those reads count as the app's.
  • The app keeps branch counts cached between repeated samples; Git alone has no cache, so that row flatters the app.
  • Listing every file also walks for empty folders, which has no Git equivalent and counts as the app's.
  • The app returns history in pages of at most 500 commits, so the longest history row asks for one whole page and Git reads the same 500. The app also reads one commit past the page to learn whether another follows, and that read counts as the app's.

A sitting counts only when other processes on the machine used at most 1.5 cores, checked before each timing of a command and its Git reads, from the CPU time each process accumulated. Machine: Apple M5 Pro, 24 GB, macOS on arm64, with Git 2.50.1 (Apple Git-155).

What Git runs on the comparison side
CommandGit alone
Working-tree statusgit status --porcelain=v2, with staged and unstaged line counts
History, first 250 commitsgit log --topo-order of 250 commits over every ref, plus unpushed and remote reads
History, first 500 commitsthe same for 500 commits, the most the app returns in one page
Branches with ahead and behindgit for-each-ref, then counts and line stats for each branch tip
Every file in the projectthree git ls-files passes: tracked, ignored and untracked
Worktrees with their statusgit worktree list, a status of each worktree four at a time, one log of their heads, and ahead and behind counts
Worktrees, 1-worktree fixturethe same, on a generated repository with one worktree of 1,000 files
Worktrees, 8-worktree fixturethe same, with eight worktrees of 1,000 files each
Open one commitseven git reads of the commit: details, notes, files, line stats and containing refs

An earlier measurement

Reading the conflict index of a 13,703-file repository, before and after an optimisation, recorded on a busier host than the sittings above. Server time only.

Median milliseconds
IndexBeforeAfter
Stale72718
Refreshed4618

Not measured yet

  • Time from launch to the first frame.
  • Memory and processor use while idle.
  • How long the screen takes to paint a reply.
  • Any machine but this Mac, including Windows and Linux.

The raw data

Every sample of every sitting, as JSON, including the commands timed without a Git comparison. Paths, build hashes and BranchHelm's own revisions are removed; the public repositories keep theirs. The repository keeps the full records, and each one's SHA-256 is listed in the file.