Skip to content

Benchmarks

These numbers measure the bus alone. Handlers only count what they get, or in v2’s own suite do nothing with it, so what’s left is the cost of queueing each message, waking the handlers’ goroutines, and waiting for room when they fall behind. Queues hold 1024 messages. Your handlers will dominate a real program; the point is that the bus adds as little as possible.

Both versions ran on the same harness, with the same handlers and queue sizes. A “16 publishers” scenario publishes from 16 goroutines at once. There, and in the suite’s Parallel rows, the time is wall time per call across all of them, so it measures throughput.

Time per call to Publish, lower is better
Show as a table
Time per call to Publish: time and allocations, v1 against v2
Scenariov1v2
1 subscriber190.9 ns2 allocs48.6 ns0 allocs
10 subscribers895.0 ns2 allocs449.3 ns0 allocs
100 subscribers13.02 µs2 allocs9.81 µs0 allocs
100 topics, 1 subscriber each146.8 ns2 allocs112.2 ns0 allocs

Apple M4 Max, Go 1.27.0, median of 5 runs.

v1 boxed every argument and called handlers through reflection, at two allocations per Publish; v2 copies the message into each queue and calls the handler directly, allocating nothing. v1 held one lock for the whole bus while it published; v2 spreads topics over 64 locks by their hash, which is where most of the gain with many publishers comes from.

With many subscribers, most of the time goes to the Go scheduler: each Publish wakes every subscriber’s goroutine that was waiting for a message. Both versions pay that.

BenchmarkTimeMemoryAllocations
Publish/1One subscriber42.8 ns0 B0
Publish/10Ten subscribers to one topic423.7 ns0 B0
Publish/100A hundred subscribers to one topic8.67 µs0 B0
PublishTopicsA hundred topics in turn, one subscriber each103.9 ns0 B0
PublishUnbufferedNew(0): each message is handed straight to the handler155.5 ns0 B0
PublishParallelPublish/1 from 16 goroutines at once102.0 ns0 B0
PublishParallelTopicsPublishTopics from 16 goroutines at once27.6 ns0 B0
SubscribeSubscribe, then unsubscribe1.09 µs9736 B6

From a clone of the repository:

Terminal window
go test -run '^$' -bench . -benchmem

To compare two versions, run each a few times and compare with benchstat:

Terminal window
go test -run '^$' -bench . -count 10 > new.txt
benchstat old.txt new.txt

The allocation counts are also enforced by tests: TestAllocations fails if Publish allocates while there is room, or while it waits with a context that can’t be cancelled.