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.
| Command | App median | App 95th | Git median | Git 95th | App ÷ Git | Budget |
|---|---|---|---|---|---|---|
| Working-tree status | 24.6 | 26.8 | 24.1 | 25.1 | 1.03× | 1.50× |
| History, first 250 commits | 485.4 | 495.5 | 453.2 | 460.6 | 1.07× | 3.00× |
| History, first 500 commits | 493.2 | 511.4 | 455.5 | 464.5 | 1.08× | 3.00× |
| Branches with ahead and behind | 5.8 | 7.3 | 5.4 | 7.1 | 1.07× | 2.00× |
| Every file in the project | 22.6 | 23.9 | 19.7 | 21 | 1.14× | 2.00× |
| Worktrees with their status | 22.9 | 25.6 | 21.7 | 23.8 | 1.06× | 1.50× |
| Worktrees, 1-worktree fixture | 12.1 | 14.1 | 11.7 | 14.4 | 1.02× | 1.50× |
| Worktrees, 8-worktree fixture | 25.2 | 28.3 | 23.9 | 26.5 | 1.05× | 1.50× |
| Open one commit | 19.8 | 23.6 | 18.9 | 21 | 1.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.
| Command | App median | App 95th | Git median | Git 95th | App ÷ Git | Budget |
|---|---|---|---|---|---|---|
| Working-tree status | 259.8 | 285.3 | 258 | 285 | 1.01× | 1.50× |
| History, first 250 commits | 7,871.6 | 8,300.6 | 7,246.5 | 7,634.3 | 1.09× | 3.00× |
| History, first 500 commits | 7,874.8 | 8,288.4 | 7,243.7 | 7,688.6 | 1.09× | 3.00× |
| Branches with ahead and behind | 14.3 | 15.7 | 13.7 | 14.6 | 1.04× | 2.00× |
| Every file in the project | 257.1 | 277.9 | 244.6 | 275.5 | 1.05× | 2.00× |
| Worktrees with their status | 7,140.5 | 7,502.4 | 7,137.7 | 7,536.1 | 1.00× | 1.50× |
| Worktrees, 1-worktree fixture | 27.8 | 30.3 | 27.2 | 28 | 1.02× | 1.50× |
| Worktrees, 8-worktree fixture | 45.4 | 50 | 44.3 | 52.6 | 1.03× | 1.50× |
| Open one commit | 6,947.6 | 7,317.4 | 6,960.8 | 7,333.2 | 1.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.
| Command | App median | App 95th | Git median | Git 95th | App ÷ Git | Budget |
|---|---|---|---|---|---|---|
| Working-tree status | 255.4 | 281.6 | 254.5 | 277.9 | 1.00× | 1.50× |
| History, first 250 commits | 940 | 966.2 | 787.4 | 812.8 | 1.19× | 3.00× |
| History, first 500 commits | 944 | 971.9 | 789.6 | 816.1 | 1.20× | 3.00× |
| Branches with ahead and behind | 12.6 | 13.1 | 12.1 | 12.7 | 1.04× | 2.00× |
| Every file in the project | 256.3 | 281.7 | 243.7 | 252.3 | 1.05× | 2.00× |
| Worktrees with their status | 209.1 | 241.8 | 208 | 228.3 | 1.01× | 1.50× |
| Worktrees, 1-worktree fixture | 27.4 | 28.7 | 27.3 | 28.5 | 1.00× | 1.50× |
| Worktrees, 8-worktree fixture | 46.1 | 49.5 | 45.1 | 49.6 | 1.03× | 1.50× |
| Open one commit | 35.9 | 37.6 | 34.7 | 36.1 | 1.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.
| Command | App median | App 95th | Git median | Git 95th | App ÷ Git | Budget |
|---|---|---|---|---|---|---|
| Working-tree status | 65.9 | 68.7 | 64.4 | 69.1 | 1.02× | 1.50× |
| History, first 250 commits | 32.4 | 36.5 | 12.9 | 15.8 | 2.48× | 3.00× |
| History, first 500 commits | 35.8 | 37.8 | 15.6 | 18.1 | 2.28× | 3.00× |
| Branches with ahead and behind | 5.9 | 7.3 | 5.8 | 7.7 | 1.02× | 2.00× |
| Every file in the project | 56.6 | 58 | 47.8 | 49.4 | 1.18× | 2.00× |
| Worktrees with their status | 48.8 | 51.6 | 47.9 | 52.2 | 1.02× | 1.50× |
| Worktrees, 1-worktree fixture | 12 | 14.1 | 11.3 | 13.9 | 1.07× | 1.50× |
| Worktrees, 8-worktree fixture | 24.7 | 28.8 | 24.2 | 28.2 | 1.02× | 1.50× |
| Open one commit | 25.4 | 29.7 | 24.5 | 27.7 | 1.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 :
| Build | App median | Git median | App ÷ Git |
|---|---|---|---|
| Before | 930.8 | 347.9 | 2.68× |
| After | 442.9 | 346 | 1.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).
| Command | Git alone |
|---|---|
| Working-tree status | git status --porcelain=v2, with staged and unstaged line counts |
| History, first 250 commits | git log --topo-order of 250 commits over every ref, plus unpushed and remote reads |
| History, first 500 commits | the same for 500 commits, the most the app returns in one page |
| Branches with ahead and behind | git for-each-ref, then counts and line stats for each branch tip |
| Every file in the project | three git ls-files passes: tracked, ignored and untracked |
| Worktrees with their status | git worktree list, a status of each worktree four at a time, one log of their heads, and ahead and behind counts |
| Worktrees, 1-worktree fixture | the same, on a generated repository with one worktree of 1,000 files |
| Worktrees, 8-worktree fixture | the same, with eight worktrees of 1,000 files each |
| Open one commit | seven 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.
| Index | Before | After |
|---|---|---|
| Stale | 727 | 18 |
| Refreshed | 46 | 18 |
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.