Benchmarks
These numbers measure gollback alone: the functions return at once, so what’s left is the cost of starting goroutines, collecting results and cancelling. Your functions will dominate a real call; the point is that gollback adds as little as possible.
In every race, one function wins at once and the others wait to be cancelled, as the slower replicas of a real request do. With 16 callers or goroutines at once, the time is wall time per call across all of them, so it measures throughput, not the latency of one call.
v2 against v1
Section titled “v2 against v1”Both versions ran on the same harness, with the same functions.
Show as a table
| Scenario | v1 | v2 |
|---|---|---|
| 1 function | 762.3 ns13 allocs | 347.5 ns5 allocs |
| 4 functions | 1.95 µs22 allocs | 1.35 µs8 allocs |
| 16 functions | 5.63 µs58 allocs | 3.94 µs20 allocs |
| 4 functions, all fail | 2.19 µs22 allocs | 1.08 µs11 allocs |
| 4 functions, 16 callers at once | 445.6 ns22 allocs | 284.4 ns8 allocs |
Apple M4 Max, Go 1.27.0, median of 5 runs.
Show as a table
| Scenario | v1 | v2 |
|---|---|---|
| 1 function | 284.5 ns5 allocs | 15.2 ns2 allocs |
| 4 functions | 950.1 ns11 allocs | 718.9 ns6 allocs |
| 16 functions | 3.08 µs35 allocs | 2.66 µs18 allocs |
| 4 functions, 16 callers at once | 313.2 ns11 allocs | 191.4 ns6 allocs |
Apple M4 Max, Go 1.27.0, median of 5 runs.
Show as a table
| Scenario | v1 | v2 |
|---|---|---|
| 5 failed attempts | 24.6 ns0 allocs | 13.4 ns0 allocs |
Apple M4 Max, Go 1.27.0, median of 5 runs.
v1 started a goroutine to collect each race and sent every result through a channel; v2 starts one goroutine per
function and nothing else, and keeps results in one place behind a lock. All runs its first function on your
goroutine, so All of one function no longer starts a goroutine at all.
v2’s own suite
Section titled “v2’s own suite”| Benchmark | Time | Memory | Allocations |
|---|---|---|---|
Race/1Race one function | 343.7 ns | 352 B | 5 |
Race/4One winner, three losers that wait to be cancelled | 1.37 µs | 448 B | 8 |
Race/16One winner, fifteen losers | 4.05 µs | 834 B | 20 |
RaceAllFailFour functions that all fail, errors joined | 1.10 µs | 600 B | 11 |
RaceParallelRace/4 from 16 goroutines at once | 273.1 ns | 448 B | 8 |
All/1One function, run on the caller's goroutine | 13.7 ns | 24 B | 2 |
All/4Four functions | 741.0 ns | 304 B | 6 |
All/16Sixteen functions | 2.76 µs | 976 B | 18 |
AllParallelAll/4 from 16 goroutines at once | 181.4 ns | 304 B | 6 |
Retry/1Succeeds on the first attempt | 2.5 ns | 0 B | 0 |
Retry/5Succeeds on the fifth attempt | 10.7 ns | 0 B | 0 |
Run them yourself
Section titled “Run them yourself”From a clone of the repository:
go test -run '^$' -bench . -benchmemTo compare two versions, run each a few times and compare with benchstat:
go test -run '^$' -bench . -count 10 > new.txtbenchstat old.txt new.txtThe allocation counts are also enforced by tests: TestAllocations fails if a call allocates more than it should.