kd daemon is one statically linked binary of about 20 MB, and the packaged downloads run 19 MB to 35 MB per platform.
Method
Two numbers bound each memory figure. The first is live resident memory, read the way the operating system reports it: ps on Linux and macOS, Task Manager on Windows. This includes memory the garbage collector has freed inside the process but has not yet returned to the operating system, so it is an upper bound. The second forces a collection and returns free pages, runtime.GC() followed by debug.FreeOSMemory(), before each reading; this is the retained working set, a lower bound. Steady-state use sits between the two.
Resident memory includes shared libraries. The kernel page cache that backs a FUSE mount is not part of process resident memory, so it is not counted here. The per-file figures come from loading files in a sweep and fitting resident memory against file count with least squares; the fits are close, with R-squared at or above 0.93. The transfer figures are the mean and peak on the receiving peer over the window of a single 1 GiB transfer.
Idle
With no files loaded, the engine resides in about 13 MiB, near identical whether the FUSE mount is up or not. The full desktop application is the engine plus the user interface, and it idles at about 116 MiB. The user interface accounts for the difference, about 103 MiB.
| Build | Mode | Idle RSS (MiB) |
|---|---|---|
engine (kd) | no-FUSE | 13.41 |
engine (kd) | FUSE | 13.65 |
| desktop app | no-FUSE | 116.43 |
| desktop app | FUSE | 116.22 |
Memory per file
Resident memory grows with the number of files a peer tracks. With no FUSE the slope is about 0.68 KiB per file. With the mount up the cost is higher, because each file also carries the attributes the kernel asks for, and the measurement splits into the two bounds above: about 2.55 KiB per file live, and about 0.84 KiB per file once the collector is forced and free pages returned.
| Mode | Per file | At 100k files | At 300k files |
|---|---|---|---|
| no-FUSE | 0.68 KiB | 80 MiB | 214 MiB |
| FUSE, compacted (lower bound) | 0.84 KiB | 89 MiB | 249 MiB |
| FUSE, live (upper bound) | 2.55 KiB | 263 MiB | 761 MiB |
The gap between the two FUSE rows is garbage-collector headroom, not a leak. The live row holds pages the collector has reclaimed but not yet handed back to the operating system; the compacted row is what remains after it does. A long-running peer sits between the two rows and drifts toward the lower one when it goes quiet.
During a transfer
Moving a large file does not scale memory with the size of the file. The receiving side reuses fixed-size buffers from a pool rather than allocating per file, so the cost of a transfer is roughly constant. On a 1 GiB transfer the receiving peer held a working set of about 102 MiB with no FUSE and 169 MiB with FUSE. That set is the streaming buffers, the gRPC windows, and the data in flight, not a copy of the file. Within the transfer window the peak ran 7.7 MiB above the mean with no FUSE and 35.6 MiB above with FUSE.
| Mode | Mean RSS | Peak RSS | Peak above mean | Throughput |
|---|---|---|---|---|
| no-FUSE | 102 MiB | 110 MiB | 7.7 MiB | 399 MB/s |
| FUSE | 169 MiB | 205 MiB | 35.6 MiB | 432 MB/s |
The buffer pool is why the working set stays flat. An earlier change to the encrypted connection replaced a per-message copy with a reused buffer and pooled the encryption scratch space, which cut heap churn on a 1 GiB transfer by about a third (commit 1012224). The figures here are downstream of that work.
Binary size
On disk, the kd daemon and command-line tool is a single statically linked binary of about 20 MB (20,445,816 bytes on the build measured). The packaged downloads are that binary plus the desktop application and its assets, wrapped for each platform, and they run from 19 MB to 35 MB. The figures below are the v0.3.5 release artifacts.
| Platform | Package | Download size |
|---|---|---|
| Windows x86-64 | .zip | 19.4 MB |
| macOS arm64 | .dmg | 32.3 MB |
| macOS arm64 | .tar.gz | 29.2 MB |
| macOS x86-64 | .tar.gz | 31.9 MB |
| Linux x86-64 | .tar.gz | 34.2 MB |
| Linux x86-64 | .deb | 34.6 MB |
About 20 MB is what a self-contained Go binary costs. There is no external runtime to install and nothing to resolve at startup. The binary carries the Go runtime, garbage collector, and scheduler; the cryptographic stack (X25519 and ML-KEM for the handshake, ChaCha20-Poly1305 for the transport); the gRPC and Protocol Buffers machinery; and the FUSE bindings. The trade is a larger file in exchange for no dependency to install. For a tool people run on three operating systems, the single file is worth the size.
What the numbers say
The footprint is bounded and predictable. An idle peer costs a few tens of MiB. A peer tracking hundreds of thousands of files costs low hundreds of MiB, set by the file count rather than by how much data has passed through it, because on-demand mode keeps file contents in a sparse cache file on disk and only the file metadata in memory. A transfer adds tens of MiB regardless of file size. On disk it is one binary of about 20 MB. None of these numbers grow with the volume of data transferred, which is the property we want from a filesystem meant to sit mounted and mostly idle.
Memory figures are live resident memory (upper bound) and GC-compacted retained set (lower bound), per-file slopes fit by least squares (R-squared at or above 0.93). Transfer figures are the receiving peer over one 1 GiB transfer. Binary sizes are the v0.3.5 release artifacts and the measured kd binary.