Performance
What kutl costs, and how that cost changes as a space gets bigger. Every measured figure comes from the one recorded run named below; the batch window, blob cap and op cap are constants from the code.
Method
Two real daemons, one real relay, one machine. The relay runs inside the test process on a loopback port, so no real network is involved. The build profile the binaries were compiled with is in the run table below. The control arm is the stock macOS rsync, run as rsync -a src/ dst/; a timed sample is a full invocation, scan included, checked against the destination afterwards.
The text corpora are files of a few dozen bytes, so per-file figures are fixed cost, not bandwidth. Corpus sizes: 50, 500, 1,000, 5,000 files. Each latency point is 30 repetitions, reported as median and p95.
The bulk stage clocks stop on a convergence barrier that polls both trees and re-reads every file on each poll, so the per-file costs in the bulk table are upper bounds that include that harness cost.
Absolute times drift by tens of percent between runs, so every table here comes from one run. A new release gets a new run, not a patched table.
| Run | |
|---|---|
| kutl | 0.6.0 |
| Recorded | 2026-09-07 |
| Host | Darwin arm64 |
| Machine | Apple M4, 24 GB, macOS 26.6.2 |
| Rust | rustc 1.96.1 (31fca3adb 2026-06-26) |
| rsync | openrsync: protocol version 29 |
| Build profile | release |
| Daemon log level while timing | info |
One save
Sync a corpus of N files and let it go quiet. Rewrite one file and stop the clock when the other machine has the bytes. Repeat 30 times.
The daemon waits up to 200 ms to batch file events before it looks at a change. Measured on loopback with the relay in the same process, the median from save to the second machine holding the bytes is 96.9 ms at 50 files and 97.1 ms at 5,000 files. kutl's own machinery therefore costs less than the debounce window it waits on. A relay reached across a network adds its round trip on top of that.
- median
- p95
- 200 ms batch window
| Files | Median | p95 | rsync -a, full pass |
|---|---|---|---|
| 50 | 96.9 ms | 110.6 ms | 8.8 ms |
| 500 | 97.6 ms | 103.7 ms | 28.8 ms |
| 1,000 | 97.1 ms | 130.6 ms | 50.6 ms |
| 5,000 | 97.1 ms | 108.5 ms | 230.4 ms |
The rsync column is the control arm, not a score. It walks the tree on every invocation and holds nothing between runs, so it is here to give the milliseconds a scale a reader already has intuitions about.
How cost grows with size
One daemon creates N files, moves them all into a subdirectory, then rewrites every one. Each stage runs until the other daemon's tree is identical. Per-file cost is what the table reports.
Our focus is on how cost changes as a space gets bigger. Each release is timed across a sweep of space sizes. A per-file cost that stays flat means the work tracks the size of the change. One that rises with the size of the space is our cue to look at resource consumption and performance regressions.
- add
- move
- edit
| Files | Add | Move | Edit |
|---|---|---|---|
| 500 | 1.2 ms | 0.5 ms | 1.4 ms |
| 1,000 | 0.8 ms | 0.5 ms | 0.8 ms |
| 5,000 | 0.7 ms | 0.2 ms | 0.9 ms |
The sweep runs at 500, 1,000, 5,000 files. Runs are kept so a later release can be read against them.
Costs and limits
- About 732 bytes on disk per document at 5,000 files, for identity and history. On this run's corpus of files a few dozen bytes each, that puts the on-disk footprint at about 20 times the logical bytes. History is not compacted, and a document caps at 10,000,000 operations.
- A daemon that has joined 5,000 documents sits at 71 MiB resident.
- A save waits up to 200 ms for the watcher's batch before kutl looks at it. That is by design: a burst of editor writes syncs once, not per keystroke.
- Binary files sync whole rather than as edits: 3 files of 16 MiB each, written in one burst, all reach the other machine in about 0.7 s. Files over 25 MiB do not sync at all: the daemon logs an error and leaves the file alone, and a synced file that grows past the cap stops updating.
- Work that touches a whole space is seconds rather than milliseconds. Joining a space of 5,000 documents takes 3.1 s before every file matches, and moving every file in it takes 1.2 s.
Not measured
Everything here is one machine over loopback; a relay across a network adds its round trip to every figure. Nothing above 5,000 files, never more than one writer at a time, macOS on Apple silicon only. The probes themselves live in kutlhub's end-to-end suite, which is not public, so these numbers are ours to keep honest rather than yours to reproduce. How correctness is tested is on the Integrity page.