Releases: bbrks/go-blurhash
v1.2.0
Hello people! It's been a while...
I've applied some very long-overdue TLC to this library and have some great news for anybody using this in the real-world. The old performance was so embarrassingly bad I'm surprised it was even usable. The new version very much is production-ready and much, much quicker.
v1.2.0 brings significant performance improvements and greatly reduced memory usage, along with a set of alloc-friendly Decoder/Encoder APIs, allowing efficient reuse of buffers for large batch jobs or long-running services.
Performance Highlights
| Operation | Improvement |
|---|---|
| Encode | ~50-140x faster (41ms → 829µs and 75ms → 534µs) |
| Decode | ~11x faster (332µs → 30µs) |
| Encode memory | 99.4% reduction (3MB → 14KB) |
| Encode allocations | 500,000 → 6 per operation |
Reusable Encoder/Decoder APIs
New Encoder and Decoder types allow reusing internal buffers across multiple operations, allowing for near zero-alloc (amortized) batch processing:
enc := blurhash.NewEncoder()
for _, img := range images {
hash, _ := enc.Encode(3, 4, img)
}
dec := blurhash.NewDecoder()
for _, hash := range hashes {
img, _ := dec.Decode(hash, 128, 128, 1)
}Other Changes
- A little modernization/cleanup for now widely available Go features
- Removed the only dependency this library had, which was test-only anyway
- Added fuzz testing (and found a few bugs with it!)
Benchmarks
benchstat v1.1.1...v1.2.0 (click to expand)
goos: linux
goarch: amd64
pkg: github.com/bbrks/go-blurhash
cpu: AMD Ryzen 7 7800X3D 8-Core Processor
│ bench-v1.1.1.txt │ bench-v1.2.0.txt │
│ sec/op │ sec/op vs base │
Components/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 8.511n ± 1% 5.570n ± 1% -34.56% (p=0.000 n=10)
Components/LNAdApj[00aymkj[TKay9}ay-Sj[-16 8.425n ± 1% 5.507n ± 2% -34.63% (p=0.000 n=10)
Components/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 8.418n ± 1% 5.485n ± 2% -34.85% (p=0.000 n=10)
Components/KJG8_@Dgx]_4V?xuyE%NRj-16 8.406n ± 1% 5.557n ± 2% -33.90% (p=0.000 n=10)
Decode/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 332.56µ ± 1% 29.69µ ± 1% -91.07% (p=0.000 n=10)
Decode/LNAdApj[00aymkj[TKay9}ay-Sj[-16 301.43µ ± 1% 30.25µ ± 1% -89.97% (p=0.000 n=10)
Decode/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 332.42µ ± 1% 29.73µ ± 1% -91.06% (p=0.000 n=10)
Decode/KJG8_@Dgx]_4V?xuyE%NRj-16 288.01µ ± 0% 24.39µ ± 1% -91.53% (p=0.000 n=10)
DecodeDraw/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 329.46µ ± 1% 28.56µ ± 1% -91.33% (p=0.000 n=10)
DecodeDraw/LNAdApj[00aymkj[TKay9}ay-Sj[-16 300.15µ ± 0% 28.89µ ± 0% -90.37% (p=0.000 n=10)
DecodeDraw/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 332.43µ ± 1% 28.56µ ± 2% -91.41% (p=0.000 n=10)
DecodeDraw/KJG8_@Dgx]_4V?xuyE%NRj-16 287.61µ ± 1% 23.24µ ± 1% -91.92% (p=0.000 n=10)
Encode/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 75498.7µ ± 1% 534.2µ ± 1% -99.29% (p=0.000 n=10)
Encode/LNAdApj[00aymkj[TKay9}ay-Sj[-16 41166.9µ ± 1% 829.3µ ± 3% -97.99% (p=0.000 n=10)
Components/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 5.570n ± 2%
Decode/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 55.47µ ± 1%
DecodeDraw/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 54.14µ ± 1%
DecoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 29.05µ ± 1%
DecoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 29.42µ ± 1%
DecoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 54.47µ ± 1%
DecoderReuse/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 28.90µ ± 1%
DecoderReuse/KJG8_@Dgx]_4V?xuyE%NRj-16 23.86µ ± 1%
DecoderDrawReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 27.80µ ± 2%
DecoderDrawReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 27.96µ ± 1%
DecoderDrawReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 52.58µ ± 1%
DecoderDrawReuse/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 27.73µ ± 1%
DecoderDrawReuse/KJG8_@Dgx]_4V?xuyE%NRj-16 22.19µ ± 1%
Encode/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 26.79m ± 4%
EncoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 536.6µ ± 1%
EncoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 814.4µ ± 3%
EncoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 26.87m ± 3%
geomean 32.44µ 17.85µ -88.19%
│ bench-v1.1.1.txt │ bench-v1.2.0.txt │
│ B/op │ B/op vs base │
Components/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Components/LNAdApj[00aymkj[TKay9}ay-Sj[-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Components/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Components/KJG8_@Dgx]_4V?xuyE%NRj-16 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
Decode/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 4.344Ki ± 0% 6.094Ki ± 0% +40.29% (p=0.000 n=10)
Decode/LNAdApj[00aymkj[TKay9}ay-Sj[-16 4.344Ki ± 0% 6.094Ki ± 0% +40.29% (p=0.000 n=10)
Decode/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 4.344Ki ± 0% 6.094Ki ± 0% +40.29% (p=0.000 n=10)
Decode/KJG8_@Dgx]_4V?xuyE%NRj-16 4.281Ki ± 0% 5.781Ki ± 0% +35.04% (p=0.000 n=10)
DecodeDraw/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 4.344Ki ± 0% 2.031Ki ± 0% -53.24% (p=0.000 n=10)
DecodeDraw/LNAdApj[00aymkj[TKay9}ay-Sj[-16 4.344Ki ± 0% 2.031Ki ± 0% -53.24% (p=0.000 n=10)
DecodeDraw/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 4.344Ki ± 0% 2.031Ki ± 0% -53.24% (p=0.000 n=10)
DecodeDraw/KJG8_@Dgx]_4V?xuyE%NRj-16 4.281Ki ± 0% 1.719Ki ± 0% -59.85% (p=0.000 n=10)
Encode/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 1951.34Ki ± 0% 11.96Ki ± 0% -99.39% (p=0.000 n=10)
Encode/LNAdApj[00aymkj[TKay9}ay-Sj[-16 3072.60Ki ± 0% 14.34Ki ± 0% -99.53% (p=0.000 n=10)
Components/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 0.000 ± 0%
Decode/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 7.188Ki ± 0%
DecodeDraw/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 3.125Ki ± 0%
DecoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 4.062Ki ± 0%
DecoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 4.062Ki ± 0%
DecoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 4.062Ki ± 0%
DecoderReuse/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 4.062Ki ± 0%
DecoderReuse/KJG8_@Dgx]_4V?xuyE%NRj-16 4.062Ki ± 0%
DecoderDrawReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 0.000 ± 0%
DecoderDrawReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 0.000 ± 0%
DecoderDrawReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 0.000 ± 0%
DecoderDrawReuse/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16 0.000 ± 0%
DecoderDrawReuse/KJG8_@Dgx]_4V?xuyE%NRj-16 0.000 ± 0%
Encode/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 80.74Ki ± 0%
EncoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16 61.00 ± 0%
EncoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16 66.00 ± 2%
EncoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16 1.992Ki ± 7%
geomean ² -58.58% ²
¹ a...
Simplify slice init to fix lint warning
v1.1.1 Simplify slice init to fix lint warning
DecodeDraw to support decoding hashes into any image type
You can decode blurhashes into any image type satisfying the image.Image interface by using the DecodeDraw function (#3)
v1.0
Decode implementation
Remove 1.13 error handling Also added go.mod file