Everything ran on one machine over loopback, to isolate software cost
All benchmarks ran on the same machine, over localhost loopback, to isolate software overhead (encryption, protocol framing, I/O layers) from network variability.
| CPU | Intel Core i7-9750H @ 2.60GHz (AES-NI) |
| RAM | 32 GB |
| OS | macOS 26.3 (x86_64) |
| Go | 1.24.3 |
| Disk | APPLE SSD (NVMe) |
Methodology: Each tool transfers a pre-allocated random file from sender to receiver over localhost. Wall-clock time is measured from "start send" to "file verified on disk at receiver." Tests run sequentially -- one tool at a time, no contention. Files are pre-generated, so their creation is not counted in the timing.
KEIBIDROP uses ::1 loopback, in-process mock relay, gRPC over ChaCha20-Poly1305 encrypted TCP. The FUSE benchmark additionally routes reads through the macFUSE kernel module.
croc runs with a local relay on 127.0.0.1:9009 (no internet). It encrypts with XSalsa20-Poly1305 behind a PAKE key exchange, and we built it from github.com/schollz/croc v10.4.2.
wormhole-william uses the public mailbox server for SPAKE2 signaling (adds ~2-3s setup latency). The data transfer itself is direct over localhost, and we built it from github.com/psanford/wormhole-william.
LocalSend protocol -- we reimplemented LocalSend's v2 transfer protocol in Go: HTTPS POST with self-signed TLS, binary body streaming. Same endpoints and flow as the real app (prepare-upload to upload). This measures the raw protocol speed rather than the Dart and Flutter app, and the source is github.com/localsend/localsend.
scp runs over the system SSH daemon on localhost, encrypting with the AES-256-GCM that SSH negotiates and the hardware accelerates.
cp is the raw disk baseline -- no encryption, no network stack.
Here is every tool at every size, in one table
| Tool | 1 MB | 10 MB | 100 MB | 1 GB |
|---|---|---|---|---|
| cp (baseline) | 20 MB/s | 208 MB/s | 1177 MB/s | 2183 MB/s |
| scp (SSH) | 15 MB/s | 175 MB/s | 1087 MB/s | 2306 MB/s |
| LocalSend AES-GCM | 175 MB/s | 440 MB/s | 617 MB/s | 704 MB/s |
| LocalSend ChaCha20 | 130 MB/s | 368 MB/s | 537 MB/s | 612 MB/s |
| KEIBIDROP gRPC | 52 MB/s | 64 MB/s | 175 MB/s | 442 MB/s |
| KEIBIDROP FUSE | 52 MB/s | 64 MB/s | 175 MB/s | 233 MB/s |
| croc (local relay) | 2.7 MB/s | 24 MB/s | 113 MB/s | 153 MB/s |
| wormhole-william | 0.4 MB/s | 3.6 MB/s | 31 MB/s | 126 MB/s |
(KEIBIDROP numbers from go test benchmarks on the same machine, same loopback. All other tools built from source and benchmarked identically.)
Half the gap was the cipher, so we ran both on the same protocol
To get an honest comparison, we ran the LocalSend protocol benchmark twice: once with Go's default TLS 1.3 (which negotiates AES-128-GCM and uses AES-NI hardware), and once forcing ChaCha20-Poly1305 -- the same cipher KEIBIDROP uses.
| LocalSend protocol (1 GB) | Cipher | Speed |
|---|---|---|
| TLS 1.3 default | AES-128-GCM (AES-NI hardware) | 704 MB/s |
| TLS 1.2 forced | ChaCha20-Poly1305 (software) | 612 MB/s |
The AES-NI advantage is 13% -- real but not the main story. Both ciphers are fast on this CPU, and the bigger factor turns out to be protocol framing.
On the same cipher, the gap is 28%
With the same cipher (ChaCha20), the comparison becomes:
| Tool (ChaCha20-Poly1305, 1 GB) | Speed | Difference |
|---|---|---|
| LocalSend protocol (HTTPS POST) | 612 MB/s | baseline |
| KEIBIDROP gRPC | 442 MB/s | -28% |
| KEIBIDROP FUSE | 233 MB/s | -62% |
The 28% gap between LocalSend protocol and KEIBIDROP gRPC comes from three sources:
- gRPC/protobuf framing: every chunk is wrapped in a protobuf message with offset, size, and metadata fields. LocalSend streams raw bytes in a single HTTP POST body with zero per-byte framing.
- Chunk-level bitmap tracking for resume support. LocalSend does not track partial downloads.
- Bidirectional multiplexing: KEIBIDROP's gRPC connection carries file transfers, notifications, heartbeats, and rekey requests simultaneously. LocalSend uses one dedicated HTTP request per file.
These are different tools solving different problems, because LocalSend is built for one-shot local transfers while KEIBIDROP maintains a persistent P2P connection with resume, notifications, and a virtual filesystem. The 28% overhead is the cost of those features.
Under 10 MB, protocol setup is most of the time
Between 1 and 10 MB the protocol setup dominates everything else. Croc spends ~400ms on PAKE key exchange and relay handshake. Wormhole spends ~2.7s on SPAKE2 through the public mailbox server. scp spends about 50 ms on its SSH handshake, and LocalSend protocol is lightweight -- an HTTPS POST with a small JSON handshake (~8ms for 1MB). KEIBIDROP has zero per-transfer setup -- the connection is already established.
Large files (1 GB): Croc tops out at 153 MB/s -- its relay-forwarding architecture adds a hop even on localhost. Wormhole hits 126 MB/s, and KEIBIDROP's gRPC hits 442 MB/s, 2.9x faster than croc. LocalSend's protocol reaches 612-704 MB/s because a single HTTPS POST body has minimal framing overhead.
FUSE overhead: KEIBIDROP through FUSE (233 MB/s) vs raw gRPC (442 MB/s) shows the 48% overhead of kernel-to-userspace context switches. That cost is inherent to any FUSE filesystem, and even with FUSE, it's 1.5x faster than croc and 1.8x faster than wormhole. The tradeoff is that FUSE gives you a mounted filesystem where you can run git, open files in editors, etc.
scp at 2.3 GB/s is reading the page cache
scp at 2.3 GB/s looks impossibly fast -- faster than cp. Both are hitting the OS page cache on localhost, because the file was written during test setup and is still in RAM. scp over localhost sends through the loopback socket with AES-256-GCM at near-zero cost (AES-NI). On a real network with wire speed limits, the difference disappears.
We reimplemented LocalSend's protocol in Go and benchmarked that
Our benchmark implements LocalSend's v2 protocol in Go -- same HTTPS endpoints, same TLS, same binary POST body. At 612 to 704 MB/s the protocol is doing well, and we did not benchmark the actual LocalSend Dart/Flutter app, so we cannot make direct speed claims about it. User reports online suggest 50-120 MB/s on wired LAN, but we haven't verified this ourselves. Our numbers describe the protocol rather than the shipped app.
We hold one connection open where croc runs a PAKE exchange per transfer
KEIBIDROP keeps one encrypted TCP connection open and multiplexes all operations over gRPC. Nothing is set up per file, where croc does a full PAKE exchange per transfer; wormhole does SPAKE2 through a mailbox server per transfer.
The StreamFile RPC pushes all chunks server-to-client with zero per-chunk round-trips, where croc and scp both work request by response instead.
Data goes straight between the two peers, where croc routes through a relay process, adding an extra hop and memory copy even on localhost.
KEIBIDROP uses ChaCha20-Poly1305, and on machines without AES-NI (ARM Macs, Linux ARM servers), ChaCha20 is faster than AES. On x86 with AES-NI, AES-GCM is 13% faster, and we chose ChaCha20 to keep performance consistent across platforms. Switching to AES-GCM would close the gap with LocalSend's protocol on x86 hardware.
Encryption costs 1.49x and FUSE costs another 1.9x
| Layer | Throughput | Overhead |
|---|---|---|
| Plain gRPC (no encryption) | 657 MB/s | baseline |
| Encrypted gRPC (ChaCha20-Poly1305) | 442 MB/s | 1.49x (33%) |
| Encrypted FUSE E2E | 233 MB/s | 2.82x (48% FUSE + 33% encryption) |
End-to-end encryption with ChaCha20-Poly1305 costs 33%, and every byte is authenticated as well as encrypted. Tools that terminate TLS at a relay (like WeTransfer) avoid this cost because the relay decrypts and re-encrypts, but the relay can then read the data.
We reach 442 MB/s over gRPC, and this is where that sits against the others
KEIBIDROP at 442 MB/s (gRPC) is 2.9x faster than croc and 3.5x faster than wormhole. The difference comes from architecture: persistent connection, no relay hop, push-based streaming.
LocalSend's protocol at 612 MB/s is 28% faster with the same cipher. The gap comes from gRPC framing and chunk tracking, and switching to AES-GCM on x86 would narrow it to ~15%.
scp is faster on localhost because the page cache eliminates disk I/O. On a real network that advantage goes away entirely.
The FUSE path at 233 MB/s adds 48% overhead from kernel-userspace context switches. This is the cost of mounting a virtual filesystem where you can run git, open files in editors, and browse shared directories.
The encrypted gRPC path (442 MB/s) is the speed for pure file transfer. Reading through FUSE brings that down to 233 MB/s.
We ran the same benchmarks again on macOS, Linux and Windows
KEIBIDROP runs on macOS, Linux and Windows, so here are localhost benchmarks across all three platforms on different hardware:
| Platform | CPU | E2E FUSE 1 GB | Encrypted gRPC 1 GB |
|---|---|---|---|
| macOS | i7-9750H @ 2.6 GHz | 240 MB/s | 452 MB/s |
| Linux | i5-8265U @ 1.6 GHz | 138 MB/s (100 MB) | 361 MB/s |
| Windows | i5-14600KF (6P+8E) | 264-296 MB/s | 749-751 MB/s |
The Linux numbers are from a lower-powered laptop (i5-8265U, 1.6 GHz base). The FUSE 1 GB benchmark was not available on Linux at the time (pre-stream-pool); the 138 MB/s is from a 100 MB transfer. The encrypted gRPC number (361 MB/s) scales with CPU speed relative to the other machines.
Windows encrypted gRPC (749-751 MB/s) is highest due to the fastest CPU. FUSE throughput across all three platforms is in the 138-296 MB/s range, confirming that FUSE kernel overhead is similar across macFUSE, Linux FUSE3, and WinFSP.
Note: these are different machines with different CPUs, so the comparison reflects combined hardware + OS differences, not OS performance alone.
Benchmark script and raw data: KD-Benchmarks