Skip to Content
Performance

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 sizeFeed query (500 notes)Timeline query (1 author)Profile queryIngest
20,000 events0.25 ms0.09 ms0.007 ms~170k events/s
100,000 events0.28 ms0.24 ms0.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 followedBeforeAfter
2046.646.0
128114.593.2
512357.8253.9
2,0481,430.11,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=ReleaseFast

BENCH_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.

Benchmarkdeednakdeed is
Startup (--version)1.9 ms121 ms64x faster
Memory at startup2.0 MB69.4 MB35x smaller
Binary size5.4 MB43.4 MB8x smaller
Verify 10,000 events32 ms1,967 ms62x faster
Sign 10,000 drafts32 ms1,414 ms44x faster
req, latest 500 notes15.9 ms226 ms14x faster
req --paginate, 49,500 notes1.34 s9.60 s7x faster
Publish 100 events to each of 4 relays46 ms280 ms6x faster
Gift wrap 1,000 messages123 ms601 ms4.9x faster
Gift unwrap 1,000 messages115 ms761 ms6.6x faster
NIP-77 sync, 50,000 events, 1% missing146 ms790 ms5.4x faster
Relay serving, publish 10,000 events1.40 s3.54 s2.5x faster
Relay serving, 32 clients each asking for the latest 500 of 10,00016.33 ms74.92 ms4.6x faster
Relay serving, 200 requests in a row on one connection99.05 ms515 ms5.2x faster
Relay serving, 32 clients, 100,000 events stored16.90 ms68.67 ms4.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.

SizeBinaryDownload
macOS, Apple Silicon (0.5.0)5.4 MB2.8 MB
macOS, Intel2.4 MB1.7 MB
Linux, x86_642.7 MB1.8 MB
Linux, aarch642.4 MB1.8 MB
Command
starting any process at all, for scale1.57 ms
deed version2.34 ms
deed event, signing one2.46 ms
deed verify, one3.05 ms
peak memory, a one-shot command1.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 npubs568,831 per second
peak memory, verifying3.9 MB
Local store, 100,000 events from 1,000 authors
storing them from a loopback relay, each signature checked17,379 per second
one event by id3.25 ms
one profile3.21 ms
one author’s latest 50 notes3.50 ms
the latest 500 notes5.03 ms
every note, 98,000 of them286 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/deed

It 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.

Last updated on