HEDDLE

Where Sley beats Git, and where it doesn't

Sley is the Git engine I wanted and couldn't find: a Rust library light enough to embed and fast enough to sit in the hot path. You link it into a process and call it directly instead of shelling out to the git binary and parsing its stdout. So the test that matters is the real one. I timed all eight everyday commands against the system git binary (2.54.0) on three real repos. The wins land where there's real work to do: log runs at half Git's time on every repo, the mid-size repo's everyday commands all roughly halve, and on Flutter the heavy ones (full status, commit) run a third to a half faster. The cheapest commands, where most of the clock is process startup, come out a wash.

The repos are all public: benhoyt/inih (61 files), we-promise/sure (2,765 files), and flutter/flutter (15,102 files). Each number is the median of fifteen timed runs (eleven for staged writes, nine for commit) after warmups, wall-clock from process start to exit, on an Apple-Silicon Mac (APFS, git 2.54.0). The runner interleaves the tools each round and rotates their order, so run position can't quietly favor one. And it checks itself: every command's output is compared to Git's outside the timing window, the same status rows, diff paths and commits, with zero mismatches across all three matrices. Nothing here is fast by doing less.

Two caveats I'll state plainly. Git's untracked-cache and fsmonitor are off for both tools, because they cache the working-tree walk across runs and what I'm measuring is the cold walk: the first run on a fresh checkout, in CI, or any process without a warmed daemon. With them on, Git's status is competitive again; that's a real config, just a different benchmark. And every number is a full CLI process, Sley's against Git's, both paying the same ~12ms of startup. Embedded, you skip that cost entirely; more on that below.

Typical command time, Sley vs Git, by repo sizeGeometric-mean median time across the eight commands, in milliseconds. Small: Git 16, Sley 13. Medium: Git 25, Sley 16. Large, the Flutter repo: Git 46, Sley 37. Git's time climbs with repo size; Sley stays below it at every size.GitSley01020304050 mssmall16 ms13 msmedium25 ms16 mslarge · Flutter46 ms37 ms
Typical command time by repo size: the geometric mean of all eight commands' median ms, so the big Flutter status walk doesn't swamp the rest. Git climbs from ~16ms to ~46ms as the repo grows; Sley stays under it at every size.

Small and mid-size: near the floor

On the small repo (benhoyt/inih) most commands finish around the process-start floor and the two trade them in the noise. The clear exception is log: Git takes about 20ms, Sley 11, and that gap holds at every size. The mid-size repo separates cleanly: status -uno, both diffs, add and add -u all fall to ~11ms against Git's ~21, a clean halving. status and commit there are washes, both near 40ms. The real spread waits for a big repo.

The wins come from the index. Sley keeps Git's semantics in typed core models (a crate for the index, one for objects, one for refs), and when a command only needs paths and modes, the index hands it a borrowed view sliced out of the on-disk file instead of allocating an owned entry per path. status streams those entries; add of a known path stages straight through the index instead of walking the worktree first. At a few hundred entries that's the whole game.

Flutter: where it pulls ahead

Flutter splits in two. The heavy commands win big: full status runs in 129ms to Git's 187, commit in 41ms to Git's 76, and log in 11ms to Git's 21, each a third to a half off. The lighter reads (status -uno, both diffs, add, add -u) are already down around 40ms for both tools and come in level, a hair either way. The commit gap is the steadiest cell on the board: 41ms against 76, within a millisecond run to run for Sley.

Full status was the last to fall. It's mostly one thing, walking the fifteen-thousand-file tree for files Git doesn't track yet, and for a long time Sley did that walk about as fast as Git and no faster, a dead heat near 190ms. Then I changed how the walk works, streaming untracked entries as they're found instead of collecting the whole tree first. Same command, same checkout: a median of 129 against Git's 187. It's the heaviest cell on the board, and once the run settled both tools held to within a couple of milliseconds, with the gap sitting near sixty.

Command time on the Flutter clone, Sley vs Git, by commandMedian time in milliseconds for each command on the Flutter repo, Git vs Sley, slowest first. status --short: Git 187, Sley 129. commit: Git 76, Sley 41. diff --name-only: Git 43, Sley 40. status -uno: Git 43, Sley 40. diff --stat: Git 42, Sley 41. add -u: Git 41, Sley 41. add: Git 21, Sley 21. log: Git 21, Sley 11.GitSley050100150200 msstatus --short187 ms129 mscommit -m76 ms41 msdiff --name-only43 ms40 msstatus -uno43 ms40 msdiff --stat42 ms41 msadd -u41 ms41 msadd <file>21 ms21 mslog --oneline21 ms12 ms
Every command on the Flutter repo, in milliseconds, Git vs Sley. commit is roughly half of Git and full status --short a third faster (129 to 187); log drops to 11 from 21. The lighter reads are already ~40ms and come in level. status --short dwarfs the rest because it walks the whole 15,000-file tree for untracked files.
commandsmallmediumlarge
status --short0.55× 21→11 ms1.00× 41→41 ms0.69× 187→129 ms
status -uno0.74× 16→12 ms0.53× 22→11 ms0.93× 43→40 ms
diff --name-only0.96× 11→11 ms0.54× 21→11 ms0.93× 43→40 ms
diff --stat0.96× 12→12 ms0.54× 21→11 ms0.99× 42→41 ms
log --oneline0.55× 20→11 ms0.55× 21→12 ms0.54× 21→12 ms
add <file>0.90× 12→10 ms0.54× 21→11 ms0.99× 21→21 ms
add -u1.02× 11→11 ms0.54× 21→11 ms0.98× 41→41 ms
commit -m1.02× 40→40 ms0.98× 41→40 ms0.54× 76→41 ms
Each cell is Git→Sley and the ratio: median ms, Sley against the system git 2.54.0, fifteen runs per read (eleven per write, nine per commit) after warmups, interleaved and rotated. The small-repo cells, and the cheapest commands generally, sit at the ~12ms process-start floor where run-to-run noise is the whole margin; the repeatable wins are the larger-margin cells, the reads on bigger repos and everything on Flutter. Every cell's output was checked against Git's: zero mismatches. Apple-Silicon Mac, APFS; untracked-cache and fsmonitor off for both; Sley @ 28555c8.

Lighter, and faster embedded

Speed isn't the only number that moved. Peak memory (the high-water mark a process touches, measured the same way with five runs a cell) runs well under half of Git's on a small repo: ~3.8 MiB to Git's ~8.4. The gap narrows as the tree grows but never closes: ~0.55× at mid-size, ~0.67× on Flutter, and Sley stays under Git on every command.

Peak memory, Sley vs Git, by repo sizeGeometric-mean peak RSS across the eight commands, in MiB. Small: Git 8.4, Sley 3.8. Medium: Git 9.3, Sley 5.1. Large, the Flutter repo: Git 13.3, Sley 8.9. Sley peaks below Git at every size.GitSley051015 MiBsmall8.4 MiB3.8 MiBmedium9.3 MiB5.1 MiBlarge · Flutter13.3 MiB8.9 MiB
Peak RSS by repo size: the geometric mean of all eight commands' median, via wait4 ru_maxrss, five runs a cell. Some of Git's floor is the binary and its linked libraries resident in memory; Sley is a single static binary. The two run-heavy Flutter commands, commit and add -u, are where the two nearly converge, both genuinely allocating then.

And it's faster than its own CLI when embedded. Everything above is the CLI paying to start a process and format text for a human. Embedded, with the repo opened once and the library called in a warm loop the way you'd actually use it, you skip both. On the small repo that overhead is most of the number: status is about 0.7ms as a library call against 11ms at the CLI, and diff --name-only is 0.1ms against 11. The work is a fraction of a millisecond; the rest is process startup and output you never render. On Flutter, where walking the tree is the actual work, the fixed cost barely shows: in-process status is ~101ms against the CLI's 129. The smaller and more frequent the call, the more embedding wins; the big slow ones are bounded by the work itself.

Parity is the gate

The cheap way to win a Git benchmark is to do less work and call it equivalent: skip the awkward edge case, approximate the output, declare victory. So correctness isn't a phase at the end here; it's the constraint the whole way through. Sley runs against Git's own test suite, the real t-files from the upstream source tree, and checks output, exit status, and side effects against the actual git binary, cell by cell. It isn't all of Git yet: it passes 19,307 of those assertions, 68% of the in-scope suite (skips excluded), across 844 of Git's test files, everything but the serving side of transport, foreign-SCM, and email. The commands you reach for first are furthest along (86% on setup and config, 73% on plumbing, 74% on branch/merge/rebase), and the per-file floors in CI only ratchet up, so a number that's green today can't quietly go red tomorrow. A fast path doesn't merge because it "seems equivalent"; it merges because it gives Git's answer and the floor held. The whole scoreboard is public, down to the individual failing cell: the Sley ⇄ Git parity report.

The pattern was the same every time: find the work Sley was doing that Git wasn't, and stop. What's left is a millisecond here and there on the cheapest commands, bounded by the filesystem and the kernel, not by Sley. That's a good place to be.


Sley is open source at github.com/HeddleCo/sley.