Brotli compression library for Go (RFC 7932), pure Go, no cgo, encoder + decoder, highly optimized
See the code
Brotli compression library for Go (RFC 7932), with encoder and decoder support.
LGWin (window size) and SizeHint (expected total input size) are exposed via WriterOptions. SizeHint lets the encoder pick context modeling and hasher parameters tuned for the actual payload size.The encoder and decoder are covered by compatibility tests and fuzzing. The public API is stable and follows semantic versioning.
go-brrr implements Brotli RFC 7932 and is tested against the Brotli reference corpus. Encoded output is byte-compatible with the C reference implementation.
| go-brrr | andybalholm | google/brotli/go/brotli | cbrotli | |
|---|---|---|---|---|
| Pure Go (no cgo) | ✓ | ✓ | ✓ | ✗ |
| Encoder | ✓ | ✓ | ✗ | ✓ |
| Decoder | ✓ | ✓ | ✓ | ✓ |
| Compound dictionaries (encode) | ✓ | ✗ | n/a | ✓ |
| Compound dictionaries (decode) | ✓ | ✗ | ✓ | ✓ |
LGWin tuning | ✓ | ✓ | n/a | ✓ |
SizeHint | ✓ | ✗ | n/a | ✗ |
Writer Reset | ✓ | ✓ | n/a | ✗ |
Reader Reset | ✓ | ✓ | ✗ | ✗ |
If you're using andybalholm/brotli, go-brrr is a near drop-in upgrade with higher throughput on both compression and decompression, plus compound-dictionary and SizeHint support. If you're using cbrotli, go-brrr gives up cgo, adds multi-chunk compound dictionaries, and exposes poolable Writer/Reader instances. cbrotli has no Reset, so each stream allocates a fresh encoder/decoder state, which is noticeable on many-small-file workloads.
go get github.com/molecule-man/go-brrr
import "github.com/molecule-man/go-brrr"
The import path is github.com/molecule-man/go-brrr; the package name is brrr.
func Example_compress() {
input := []byte("Hello, brotli!")
var compressed bytes.Buffer
w, err := brrr.NewWriter(&compressed, 6)
if err != nil {
log.Fatal(err)
}
if _, err := w.Write(input); err != nil {
log.Fatal(err)
}
if err := w.Close(); err != nil {
log.Fatal(err)
}
}
More examples are available in example_test.go and the Go package docs: round-trip compression and decompression, one-shot decompression, reusing writers and readers, pooling, and compound dictionaries.
The best use case for brotli is static asset compression - CSS, JS, HTML, fonts, WASM - where you compress once at build time and serve the result millions of times. Use quality 11 for this: speed doesn't matter because you pay the cost once, and brotli q11 delivers ratios that neither gzip nor zstd can match. Every browser shipped since 2016 supports Content-Encoding: br.
For on-the-fly compression, brotli q5–6 is a strong choice if you're already using zstd at its highest level: q5 is often faster with a better ratio, and q6 is only slightly slower with an even better ratio. At lower compression levels, zstd is significantly faster - if throughput is your priority and you don't need the best ratio, zstd is the better tool for the job.
If you compress or decompress repeatedly (e.g. per request in a webserver), keep *brrr.Writer and *brrr.Reader instances in sync.Pools and Reset each one into the next stream rather than allocating new instances each time. See the compiled examples.
go-brrr is optimized for throughput. Some hot paths intentionally use larger functions, duplicated loops, and specialized code where benchmarks showed measurable wins. These choices stay local to performance-sensitive encoder and decoder internals; public APIs stay small and conventional.
Contributions are welcome. Read CONTRIBUTING.md before you start.
The most important rules:
The maintainer measures every performance change against a file corpus on dedicated hardware.
For a large optimization, open an issue first and agree on the scope.
This library is a port of the Brotli reference implementation by the Brotli Authors, licensed under the MIT License.
All benchmarks were taken on the following setup with turboboost, etc, being disabled via denoise-amd.sh:
goos: linux
goarch: amd64
cpu: AMD Ryzen 5 7535HS with Radeon Graphics
Compared against klauspost/compress zstd (pure Go) and stdlib gzip. Single CPU, no parallelism. These plots measure reused streaming encoders: the timed loop resets a warmed writer and discards compressed output, while ratio is measured from a warmup buffer.
| Compression | Decompression |
|---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Compared against other Go brotli libraries. go-brrr is the base in all comparisons. The smaller the number the better.
The table below measures end-to-end throughput through each package's public Go API for many independent brotli streams, not only the inner compression loop. Each payload is written as a complete stream with a fresh public writer instance so cbrotli, which has no resettable writer API, can be included.
go-brrr still benefits from internal reuse in that shape: encoder arenas, hashers, hash tables, and scratch buffers are kept reusable through reset paths and internal sync.Pools. That avoids repeated large allocations and zeroing, which matters for small and mid-size payloads. cbrotli uses the C reference encoder underneath, but each payload creates a new BrotliEncoderState through cbrotli.NewWriter and destroys it on Close, paying setup, teardown, cgo, and allocation costs for every stream.
Read these rows as repeated complete-stream compression through the Go APIs. They are not a claim that every pure-Go compression hot path is faster than the C implementation; the same table shows quality levels where cbrotli is faster.
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | |
|---|---|---|---|
| CompressOneshot/q=0/payload=VariedPayloads | 6.515m ± 1% | 12.420m ± 0% +90.64% (p=0.000 n=8) | 6.847m ± 0% +5.10% (p=0.000 n=8) |
| CompressOneshot/q=1/payload=VariedPayloads | 9.797m ± 0% | 20.169m ± 0% +105.88% (p=0.000 n=8) | 10.808m ± 0% +10.32% (p=0.000 n=8) |
| CompressOneshot/q=2/payload=VariedPayloads | 14.68m ± 0% | 38.86m ± 2% +164.76% (p=0.000 n=8) | 17.93m ± 0% +22.14% (p=0.000 n=8) |
| CompressOneshot/q=3/payload=VariedPayloads | 16.06m ± 0% | 44.34m ± 1% +176.12% (p=0.000 n=8) | 20.85m ± 0% +29.87% (p=0.000 n=8) |
| CompressOneshot/q=4/payload=VariedPayloads | 25.68m ± 0% | 61.37m ± 1% +138.97% (p=0.000 n=8) | 29.96m ± 0% +16.67% (p=0.000 n=8) |
| CompressOneshot/q=5/payload=VariedPayloads | 36.41m ± 0% | 80.19m ± 1% +120.22% (p=0.000 n=8) | 47.13m ± 0% +29.43% (p=0.000 n=8) |
| CompressOneshot/q=6/payload=VariedPayloads | 43.87m ± 0% | 90.57m ± 1% +106.47% (p=0.000 n=8) | 54.78m ± 0% +24.88% (p=0.000 n=8) |
| CompressOneshot/q=7/payload=VariedPayloads | 50.34m ± 0% | 127.34m ± 1% +152.96% (p=0.000 n=8) | 110.24m ± 0% +119.00% (p=0.000 n=8) |
| CompressOneshot/q=8/payload=VariedPayloads | 58.95m ± 0% | 146.62m ± 1% +148.72% (p=0.000 n=8) | 81.03m ± 1% +37.46% (p=0.000 n=8) |
| CompressOneshot/q=9/payload=VariedPayloads | 74.02m ± 0% | 209.77m ± 2% +183.41% (p=0.000 n=8) | 237.70m ± 0% +221.15% (p=0.000 n=8) |
| CompressOneshot/q=10/payload=VariedPayloads | 1221.7m ± 2% | 1368.5m ± 1% +12.01% (p=0.000 n=8) | 864.3m ± 0% -29.26% (p=0.000 n=8) |
| CompressOneshot/q=11/payload=VariedPayloads | 2.939 ± 1% | 3.424 ± 1% +16.51% (p=0.000 n=8) | 2.266 ± 0% -22.89% (p=0.000 n=8) |
| geomean | 52.96m | 111.1m +109.76% | 67.47m +27.40% |
Streaming uses brrr.NewReader + io.ReadAll; one-shot uses brrr.Decompress on a complete in-memory blob.
As cbrotli doesn't have the "resettable" API it's not included here.
| go-brrr (sec/op) | andybalholm (sec/op) | |
|---|---|---|
| Decompress/q=4/payload=VariedPayloads | 5.378m ± 0% | 9.539m ± 0% +77.36% |
| Decompress/q=5/payload=VariedPayloads | 5.302m ± 0% | 9.143m ± 0% +72.43% |
| Decompress/q=6/payload=VariedPayloads | 5.146m ± 0% | 8.881m ± 0% +72.56% |
| Decompress/q=11/payload=VariedPayloads | 5.621m ± 0% | 8.959m ± 0% +59.37% |
| geomean | 5.359m | 9.127m +70.30% |
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | google-brotli (sec/op) | |
|---|---|---|---|---|
| DecompressOneshot/q=4/payload=VariedPayloads | 5.458m ± 0% | 10.042m ± 0% +84.01% | 5.191m ± 2% -4.89% | 10.595m ± 0% +94.13% |
| DecompressOneshot/q=5/payload=VariedPayloads | 5.458m ± 1% | 9.609m ± 0% +76.07% | 5.022m ± 11% ~ | 10.541m ± 0% +93.14% |
| DecompressOneshot/q=6/payload=VariedPayloads | 5.329m ± 1% | 9.384m ± 0% +76.11% | 4.916m ± 4% -7.74% | 10.240m ± 1% +92.17% |
| DecompressOneshot/q=11/payload=VariedPayloads | 5.816m ± 1% | 9.540m ± 0% +64.03% | 6.981m ± 1% +20.03% | crashed |
| geomean | 5.512m | 9.641m +74.91% | 5.469m -0.78% | 10.46m +93.14% |
The VariedPayloads benchmark rotates through a heterogeneous mix of files, guarding against benchmark-shaped optimizations - wins that only show up when the same input is fed back-to-back should not move these rows. Payloads span small JSON API responses, mid-size HTML and JS bundles, and larger English prose, drawn from the Brotli reference test corpus and the local testdata/ directory.
| File | Size | Source |
|---|---|---|
| github_events_2k.json | 2.2 KB | testdata |
| github_events_5k.json | 5.2 KB | testdata |
| github_events_8k.json | 8.3 KB | testdata |
| asyoulik.txt | 122 KB | brotli-ref |
| alice29.txt | 149 KB | brotli-ref |
| gh_172KB.html | 167 KB | testdata |
| reactcore_187KB.js | 182 KB | testdata |
| lcet10.txt | 417 KB | brotli-ref |
| plrabn12.txt | 471 KB | brotli-ref |
Go
96.6%
Shell
2.2%
Assembly
1.0%
Brotli compression library for Go (RFC 7932), pure Go, no cgo, encoder + decoder, highly optimized
See the code
Brotli compression library for Go (RFC 7932), with encoder and decoder support.
LGWin (window size) and SizeHint (expected total input size) are exposed via WriterOptions. SizeHint lets the encoder pick context modeling and hasher parameters tuned for the actual payload size.The encoder and decoder are covered by compatibility tests and fuzzing. The public API is stable and follows semantic versioning.
go-brrr implements Brotli RFC 7932 and is tested against the Brotli reference corpus. Encoded output is byte-compatible with the C reference implementation.
| go-brrr | andybalholm | google/brotli/go/brotli | cbrotli | |
|---|---|---|---|---|
| Pure Go (no cgo) | ✓ | ✓ | ✓ | ✗ |
| Encoder | ✓ | ✓ | ✗ | ✓ |
| Decoder | ✓ | ✓ | ✓ | ✓ |
| Compound dictionaries (encode) | ✓ | ✗ | n/a | ✓ |
| Compound dictionaries (decode) | ✓ | ✗ | ✓ | ✓ |
LGWin tuning | ✓ | ✓ | n/a | ✓ |
SizeHint | ✓ | ✗ | n/a | ✗ |
Writer Reset | ✓ | ✓ | n/a | ✗ |
Reader Reset | ✓ | ✓ | ✗ | ✗ |
If you're using andybalholm/brotli, go-brrr is a near drop-in upgrade with higher throughput on both compression and decompression, plus compound-dictionary and SizeHint support. If you're using cbrotli, go-brrr gives up cgo, adds multi-chunk compound dictionaries, and exposes poolable Writer/Reader instances. cbrotli has no Reset, so each stream allocates a fresh encoder/decoder state, which is noticeable on many-small-file workloads.
go get github.com/molecule-man/go-brrr
import "github.com/molecule-man/go-brrr"
The import path is github.com/molecule-man/go-brrr; the package name is brrr.
func Example_compress() {
input := []byte("Hello, brotli!")
var compressed bytes.Buffer
w, err := brrr.NewWriter(&compressed, 6)
if err != nil {
log.Fatal(err)
}
if _, err := w.Write(input); err != nil {
log.Fatal(err)
}
if err := w.Close(); err != nil {
log.Fatal(err)
}
}
More examples are available in example_test.go and the Go package docs: round-trip compression and decompression, one-shot decompression, reusing writers and readers, pooling, and compound dictionaries.
The best use case for brotli is static asset compression - CSS, JS, HTML, fonts, WASM - where you compress once at build time and serve the result millions of times. Use quality 11 for this: speed doesn't matter because you pay the cost once, and brotli q11 delivers ratios that neither gzip nor zstd can match. Every browser shipped since 2016 supports Content-Encoding: br.
For on-the-fly compression, brotli q5–6 is a strong choice if you're already using zstd at its highest level: q5 is often faster with a better ratio, and q6 is only slightly slower with an even better ratio. At lower compression levels, zstd is significantly faster - if throughput is your priority and you don't need the best ratio, zstd is the better tool for the job.
If you compress or decompress repeatedly (e.g. per request in a webserver), keep *brrr.Writer and *brrr.Reader instances in sync.Pools and Reset each one into the next stream rather than allocating new instances each time. See the compiled examples.
go-brrr is optimized for throughput. Some hot paths intentionally use larger functions, duplicated loops, and specialized code where benchmarks showed measurable wins. These choices stay local to performance-sensitive encoder and decoder internals; public APIs stay small and conventional.
Contributions are welcome. Read CONTRIBUTING.md before you start.
The most important rules:
The maintainer measures every performance change against a file corpus on dedicated hardware.
For a large optimization, open an issue first and agree on the scope.
This library is a port of the Brotli reference implementation by the Brotli Authors, licensed under the MIT License.
All benchmarks were taken on the following setup with turboboost, etc, being disabled via denoise-amd.sh:
goos: linux
goarch: amd64
cpu: AMD Ryzen 5 7535HS with Radeon Graphics
Compared against klauspost/compress zstd (pure Go) and stdlib gzip. Single CPU, no parallelism. These plots measure reused streaming encoders: the timed loop resets a warmed writer and discards compressed output, while ratio is measured from a warmup buffer.
| Compression | Decompression |
|---|---|
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
Compared against other Go brotli libraries. go-brrr is the base in all comparisons. The smaller the number the better.
The table below measures end-to-end throughput through each package's public Go API for many independent brotli streams, not only the inner compression loop. Each payload is written as a complete stream with a fresh public writer instance so cbrotli, which has no resettable writer API, can be included.
go-brrr still benefits from internal reuse in that shape: encoder arenas, hashers, hash tables, and scratch buffers are kept reusable through reset paths and internal sync.Pools. That avoids repeated large allocations and zeroing, which matters for small and mid-size payloads. cbrotli uses the C reference encoder underneath, but each payload creates a new BrotliEncoderState through cbrotli.NewWriter and destroys it on Close, paying setup, teardown, cgo, and allocation costs for every stream.
Read these rows as repeated complete-stream compression through the Go APIs. They are not a claim that every pure-Go compression hot path is faster than the C implementation; the same table shows quality levels where cbrotli is faster.
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | |
|---|---|---|---|
| CompressOneshot/q=0/payload=VariedPayloads | 6.515m ± 1% | 12.420m ± 0% +90.64% (p=0.000 n=8) | 6.847m ± 0% +5.10% (p=0.000 n=8) |
| CompressOneshot/q=1/payload=VariedPayloads | 9.797m ± 0% | 20.169m ± 0% +105.88% (p=0.000 n=8) | 10.808m ± 0% +10.32% (p=0.000 n=8) |
| CompressOneshot/q=2/payload=VariedPayloads | 14.68m ± 0% | 38.86m ± 2% +164.76% (p=0.000 n=8) | 17.93m ± 0% +22.14% (p=0.000 n=8) |
| CompressOneshot/q=3/payload=VariedPayloads | 16.06m ± 0% | 44.34m ± 1% +176.12% (p=0.000 n=8) | 20.85m ± 0% +29.87% (p=0.000 n=8) |
| CompressOneshot/q=4/payload=VariedPayloads | 25.68m ± 0% | 61.37m ± 1% +138.97% (p=0.000 n=8) | 29.96m ± 0% +16.67% (p=0.000 n=8) |
| CompressOneshot/q=5/payload=VariedPayloads | 36.41m ± 0% | 80.19m ± 1% +120.22% (p=0.000 n=8) | 47.13m ± 0% +29.43% (p=0.000 n=8) |
| CompressOneshot/q=6/payload=VariedPayloads | 43.87m ± 0% | 90.57m ± 1% +106.47% (p=0.000 n=8) | 54.78m ± 0% +24.88% (p=0.000 n=8) |
| CompressOneshot/q=7/payload=VariedPayloads | 50.34m ± 0% | 127.34m ± 1% +152.96% (p=0.000 n=8) | 110.24m ± 0% +119.00% (p=0.000 n=8) |
| CompressOneshot/q=8/payload=VariedPayloads | 58.95m ± 0% | 146.62m ± 1% +148.72% (p=0.000 n=8) | 81.03m ± 1% +37.46% (p=0.000 n=8) |
| CompressOneshot/q=9/payload=VariedPayloads | 74.02m ± 0% | 209.77m ± 2% +183.41% (p=0.000 n=8) | 237.70m ± 0% +221.15% (p=0.000 n=8) |
| CompressOneshot/q=10/payload=VariedPayloads | 1221.7m ± 2% | 1368.5m ± 1% +12.01% (p=0.000 n=8) | 864.3m ± 0% -29.26% (p=0.000 n=8) |
| CompressOneshot/q=11/payload=VariedPayloads | 2.939 ± 1% | 3.424 ± 1% +16.51% (p=0.000 n=8) | 2.266 ± 0% -22.89% (p=0.000 n=8) |
| geomean | 52.96m | 111.1m +109.76% | 67.47m +27.40% |
Streaming uses brrr.NewReader + io.ReadAll; one-shot uses brrr.Decompress on a complete in-memory blob.
As cbrotli doesn't have the "resettable" API it's not included here.
| go-brrr (sec/op) | andybalholm (sec/op) | |
|---|---|---|
| Decompress/q=4/payload=VariedPayloads | 5.378m ± 0% | 9.539m ± 0% +77.36% |
| Decompress/q=5/payload=VariedPayloads | 5.302m ± 0% | 9.143m ± 0% +72.43% |
| Decompress/q=6/payload=VariedPayloads | 5.146m ± 0% | 8.881m ± 0% +72.56% |
| Decompress/q=11/payload=VariedPayloads | 5.621m ± 0% | 8.959m ± 0% +59.37% |
| geomean | 5.359m | 9.127m +70.30% |
| go-brrr (sec/op) | andybalholm (sec/op) | cbrotli (sec/op) | google-brotli (sec/op) | |
|---|---|---|---|---|
| DecompressOneshot/q=4/payload=VariedPayloads | 5.458m ± 0% | 10.042m ± 0% +84.01% | 5.191m ± 2% -4.89% | 10.595m ± 0% +94.13% |
| DecompressOneshot/q=5/payload=VariedPayloads | 5.458m ± 1% | 9.609m ± 0% +76.07% | 5.022m ± 11% ~ | 10.541m ± 0% +93.14% |
| DecompressOneshot/q=6/payload=VariedPayloads | 5.329m ± 1% | 9.384m ± 0% +76.11% | 4.916m ± 4% -7.74% | 10.240m ± 1% +92.17% |
| DecompressOneshot/q=11/payload=VariedPayloads | 5.816m ± 1% | 9.540m ± 0% +64.03% | 6.981m ± 1% +20.03% | crashed |
| geomean | 5.512m | 9.641m +74.91% | 5.469m -0.78% | 10.46m +93.14% |
The VariedPayloads benchmark rotates through a heterogeneous mix of files, guarding against benchmark-shaped optimizations - wins that only show up when the same input is fed back-to-back should not move these rows. Payloads span small JSON API responses, mid-size HTML and JS bundles, and larger English prose, drawn from the Brotli reference test corpus and the local testdata/ directory.
| File | Size | Source |
|---|---|---|
| github_events_2k.json | 2.2 KB | testdata |
| github_events_5k.json | 5.2 KB | testdata |
| github_events_8k.json | 8.3 KB | testdata |
| asyoulik.txt | 122 KB | brotli-ref |
| alice29.txt | 149 KB | brotli-ref |
| gh_172KB.html | 167 KB | testdata |
| reactcore_187KB.js | 182 KB | testdata |
| lcet10.txt | 417 KB | brotli-ref |
| plrabn12.txt | 471 KB | brotli-ref |
Go
96.6%
Shell
2.2%
Assembly
1.0%