Improving speed of SMAZ compressor by 2.6x/1.5x

Improving speed of SMAZ compressor by 2.6x/1.5x

part of Go Cookbook
I was testing fast compressors in pure Go. One of them was a Go implementation of the SMAZ algorithm for compressing small strings. It’s simple, fast and works well for English text.
It wasn’t as fast as I expected so I looked at the code and with a few tweaks I managed to speed up decompression 2.61 times and compression 1.54 times:
kjkmacpro:smaz kjk$ benchcmp before.txt after.txt
benchmark                  old ns/op     new ns/op     delta
BenchmarkCompression       3387936       2195304       -35.20%
BenchmarkDecompression     2667583       1022908       -61.65%

benchmark                  old MB/s     new MB/s     speedup
BenchmarkCompression       40.35        62.26        1.54x
BenchmarkDecompression     28.34        73.90        2.61x
These numbers describe the original 2014 experiment. They have not been remeasured with the current compiler and runtime. The speed increase came from three micro-optimizations.

1. Don’t use bytes.Buffer if []byte will do.

The biggest decompression speed-up came from this change where I replaced the use of bytes.Buffer with a []byte slice.
bytes.Buffer is a wrapper around []byte. It adds convenience by implementing popular interfaces like io.Reader, io.Writer etc. In this benchmark, using the slice directly reduced overhead. That is a measurement for this loop, not a rule that bytes.Buffer is always slower.
It doesn’t matter in most programs, but in a tight decompression loop even small wins do add up.

2. Reusing buffers is another common optimization trick in Go.

The original API was:
compressed := smaz.Compress(source)
The Compress function has no option but to allocate a new buffer for the compressed data. Allocations are not free and they slow down the program by making the garbage collector do more work.
Other compression libraries allow the caller to provide a buffer for the result:
compressed := make([]byte, 1024)
compressed = smaz.Encode(compressed, source)
If the buffer is not big enough, it’ll be enlarged. If the caller doesn’t want to manage the buffer, it can pass nil.

3. Avoid unnecessary copies

Compression and decompression involve reading data from memory, transforming it and writing the result to another memory location.
Memory access can dominate a tight loop. Its cost depends on cache locality, the processor, and how much work can overlap, so a fixed ratio of CPU instructions to memory operations is not generally useful.
I noticed that compression was making unnecessary temporary copies of data. The code got a bit more complicated but also 1.14x faster.

Benchmarking tools in Go

Go includes tools for writing benchmarks and tests.
Go 1.24 added testing.B.Loop, which excludes setup before the loop and cleanup after it from the measurement. Here is the compression benchmark using that API (loadTestData and Encode come from the compressor package):
func BenchmarkCompression(b *testing.B) {
	inputs, n := loadTestData(b)
	b.SetBytes(n)
	var dst []byte
	b.ReportAllocs()
	for b.Loop() {
		for _, input := range inputs {
			dst = Encode(dst, input)
		}
	}
}
You run benchmarks with go test -bench=.. To select a benchmark, use the -bench argument (or pass . to run all of them).
Go minimizes the amount of work the programmer needs to do in several ways:
Use benchstat to compare repeated measurements. Install command-line tools with go install ...@version, rather than go get:
go install golang.org/x/perf/cmd/benchstat@latest
go test -run=^$ -bench=. -benchmem -count=10 > before.txt
# Make the change, then repeat on the same machine and toolchain.
go test -run=^$ -bench=. -benchmem -count=10 > after.txt
benchstat before.txt after.txt
The historical output at the top used benchcmp; benchstat is the tool to use for a new comparison. Measure representative inputs and inspect both timing and allocation changes. Buffer reuse also changes ownership: do not retain a result while reusing its backing buffer for the next call.
#go #programming
Sep 5 2026

Related articles

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you: