Performance
Performance is a standing design goal, not an afterthought. These are
zig-nostr’s own numbers, measured with the in-repo benchmark and reproducible
on your own machine. No competitor comparisons; just what the library does.
Local store: query & ingest
Measured on an Apple Silicon Mac, ReleaseFast, warm cache, best of 50 runs.
Events are spread across 100 authors; the feed query is a 20-author, kind:1
home timeline returning 500 notes.
| Store size | Feed query (500 notes) | Timeline query (1 author) | Profile query | Ingest |
|---|---|---|---|---|
| 20,000 events | 0.25 ms | 0.09 ms | 0.007 ms | ~170k events/s |
| 100,000 events | 0.28 ms | 0.24 ms | 0.008 ms | ~149k events/s |
The headline isn’t just that a feed query takes ~0.28 ms: it’s that going
from 20k to 100k stored events barely moves it. The profile query makes the same
point sharply: fetching one account’s kind:0 out of a hundred thousand events
reads a single index entry, so it costs 8 microseconds and does not care how
much history sits behind it. That’s the bounded query
planner: it walks the indexes newest-first and stops at limit, so latency
tracks the page size you ask for, not the size of the store.
A long follow list
A client that follows hundreds of people queries for all of them at once, and the store merges one stream per author, newest first. The merge takes the newest candidate from a binary heap of the live streams rather than scanning all of them for every note it returns, so adding authors costs a sift of the heap and not another pass over every author. The order of the results is unchanged. This is a 60-note kind:1 query with one stream per author, in microseconds, ReleaseFast, best of five runs, before and after the heap.
| Authors followed | Before | After |
|---|---|---|
| 20 | 46.6 | 46.0 |
| 128 | 114.5 | 93.2 |
| 512 | 357.8 | 253.9 |
| 2,048 | 1,430.1 | 1,006.4 |
What remains at large follow lists is mostly opening a cursor per author, which a heap does not change.
Reproduce the library numbers
git clone https://github.com/zig-nostr/nostr
cd nostr
BENCH_N=100000 zig build bench -Doptimize=ReleaseFastBENCH_AUTHORS sets how many authors the store is spread across, which is also how many streams the wide feed query merges.
The benchmark source is
src/bench.zig.
Absolute numbers vary with hardware; the shape, flat feed latency as the
store grows, is the point.
deed and nak
deed 0.5.0 beats nak v0.21.2 on every benchmark run here: faster or smaller on all 53 comparisons where both have the feature, with no ties. Times are the minimum of N runs, release builds of both, on an Apple M2 Pro over loopback.
| Benchmark | deed | nak | deed is |
|---|---|---|---|
Startup (--version) | 1.9 ms | 121 ms | 64x faster |
| Memory at startup | 2.0 MB | 69.4 MB | 35x smaller |
| Binary size | 5.4 MB | 43.4 MB | 8x smaller |
| Verify 10,000 events | 32 ms | 1,967 ms | 62x faster |
| Sign 10,000 drafts | 32 ms | 1,414 ms | 44x faster |
req, latest 500 notes | 15.9 ms | 226 ms | 14x faster |
req --paginate, 49,500 notes | 1.34 s | 9.60 s | 7x faster |
| Publish 100 events to each of 4 relays | 46 ms | 280 ms | 6x faster |
| Gift wrap 1,000 messages | 123 ms | 601 ms | 4.9x faster |
| Gift unwrap 1,000 messages | 115 ms | 761 ms | 6.6x faster |
| NIP-77 sync, 50,000 events, 1% missing | 146 ms | 790 ms | 5.4x faster |
| Relay serving, publish 10,000 events | 1.40 s | 3.54 s | 2.5x faster |
| Relay serving, 32 clients each asking for the latest 500 of 10,000 | 16.33 ms | 74.92 ms | 4.6x faster |
| Relay serving, 200 requests in a row on one connection | 99.05 ms | 515 ms | 5.2x faster |
| Relay serving, 32 clients, 100,000 events stored | 16.90 ms | 68.67 ms | 4.1x faster |
Both tools ran the same work on the same fixtures, signed with throwaway keys. Fixtures and relays bind 127.0.0.1 only: nak’s relay is nak serve, and deed was checked to produce the same output as nak for each row (event counts, cross-unwrapping of each other’s gift wraps, cross-decrypting each other’s payloads). Timing uses hyperfine -N with a warm-up, and the minimum is reported because noise on a shared machine only adds time. A comparison within 5 percent counts as a tie. Rows where nak lacks the feature, such as reading from a local store, are not counted. The full table, with every row and the method, is in deed’s BENCHMARKS.md. The relay serving rows compare deed serve with nak serve under the same client; nak serve caps a request at 500 events, so each query asks for 500, and the relay is put under load with 32 clients at once or 200 requests in a row, since a single query mostly measures the client.
deed
What the command line costs to run, measured on an Apple M2 Pro with the deed 0.3.1 binary from the release page (ReleaseSafe, stripped). Each number is the best of several runs, and every one is the whole command, starting the process included. The macOS Apple Silicon size and the two 10,000-event stream rates are from deed 0.5.0; the other rows are from 0.3.1.
| Size | Binary | Download |
|---|---|---|
| macOS, Apple Silicon (0.5.0) | 5.4 MB | 2.8 MB |
| macOS, Intel | 2.4 MB | 1.7 MB |
| Linux, x86_64 | 2.7 MB | 1.8 MB |
| Linux, aarch64 | 2.4 MB | 1.8 MB |
| Command | |
|---|---|
| starting any process at all, for scale | 1.57 ms |
deed version | 2.34 ms |
deed event, signing one | 2.46 ms |
deed verify, one | 3.05 ms |
| peak memory, a one-shot command | 1.7 MB |
| Streams of 10,000 through stdin | |
|---|---|
| signing (0.5.0) | 309,449 per second |
| verifying (0.5.0) | 312,868 per second |
| decoding npubs | 568,831 per second |
| peak memory, verifying | 3.9 MB |
| Local store, 100,000 events from 1,000 authors | |
|---|---|
| storing them from a loopback relay, each signature checked | 17,379 per second |
| one event by id | 3.25 ms |
| one profile | 3.21 ms |
| one author’s latest 50 notes | 3.50 ms |
| the latest 500 notes | 5.03 ms |
| every note, 98,000 of them | 286 ms |
A lookup costs about a millisecond more than deed version does, starting deed and opening the store included. The store’s own share is the fraction of a millisecond the library numbers at the top of this page show.
git clone https://github.com/zig-nostr/deed
cd deed
zig build -Doptimize=ReleaseSafe -Dstrip=true
python3 bench/run.py zig-out/bin/deedIt needs python3 and websocat, and talks only to a relay it runs on 127.0.0.1. The script is bench/run.py, and BENCHMARKS.md has every number it prints.