Skip to content

Benchmarks

These numbers measure progress-go alone. Advance draws to io.Discard, so no output is counted. A whole bar draws to /dev/null, a character device that both versions treat as a terminal, so each frame costs a write system call, as it would on a real terminal. With 16 goroutines at once, the time is wall time per call across all of them, so it measures throughput.

Both versions ran on the same harness, with the same scenarios. The untagged Bar isn’t safe from many goroutines, so with 16 at once it runs behind a mutex, as its callers had to.

Time per Advance, lower is better
Show as a table
Time per Advance: time and allocations, untagged against v1
Scenariountaggedv1
1 goroutine133.0 ns1 alloc1.8 ns0 allocs
16 goroutines at once313.2 ns2 allocs59.6 ns0 allocs
Verbose, 1 goroutine23.56 µs13 allocs1.8 ns0 allocs

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

The untagged version redrew the whole line on every Advance, formatting it with fmt and growing the bar by string concatenation, and a verbose bar called runtime.ReadMemStats, which stops the world, each time. v1’s Advance is one atomic add. On a terminal a timer redraws the bar ten times a second; a bar of 1,000 fast updates draws two frames, at Start and at Stop, where the untagged version drew 1,002.

BenchmarkTimeMemoryAllocations
AdvanceOne atomic add1.8 ns0 B0
AdvanceParallelOne bar, advanced from 16 goroutines at once60.7 ns0 B0
RedrawOne frame on a terminal, in one Write44.3 ns0 B0
RedrawVerboseOne verbose frame: heap size and times268.9 ns0 B0
BarNew, Start, 1,000 Advances and Stop2.00 µs848 B5

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 a call allocates more than it should.