Skip to content

v1.2.0

Latest

Choose a tag to compare

@bbrks bbrks released this 30 Jan 01:20
07a73a7

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%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                                                           │ bench-v1.1.1.txt  │           bench-v1.2.0.txt            │
                                                                           │     allocs/op     │ allocs/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                                            3.000 ± 0%     5.000 ± 0%   +66.67% (p=0.000 n=10)
Decode/LNAdApj[00aymkj[TKay9}ay-Sj[-16                                            3.000 ± 0%     5.000 ± 0%   +66.67% (p=0.000 n=10)
Decode/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16                                            3.000 ± 0%     5.000 ± 0%   +66.67% (p=0.000 n=10)
Decode/KJG8_@Dgx]_4V?xuyE%NRj-16                                                  3.000 ± 0%     5.000 ± 0%   +66.67% (p=0.000 n=10)
DecodeDraw/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16                                        3.000 ± 0%     3.000 ± 0%         ~ (p=1.000 n=10) ¹
DecodeDraw/LNAdApj[00aymkj[TKay9}ay-Sj[-16                                        3.000 ± 0%     3.000 ± 0%         ~ (p=1.000 n=10) ¹
DecodeDraw/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16                                        3.000 ± 0%     3.000 ± 0%         ~ (p=1.000 n=10) ¹
DecodeDraw/KJG8_@Dgx]_4V?xuyE%NRj-16                                              3.000 ± 0%     3.000 ± 0%         ~ (p=1.000 n=10) ¹
Encode/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16                                       499441.000 ± 0%     6.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/LNAdApj[00aymkj[TKay9}ay-Sj[-16                                       786481.000 ± 0%     6.000 ± 0%  -100.00% (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                                 5.000 ± 0%
DecodeDraw/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16                             3.000 ± 0%
DecoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16                                                     2.000 ± 0%
DecoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16                                                     2.000 ± 0%
DecoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16                           2.000 ± 0%
DecoderReuse/LNMF%n00%#MwS|WCWEM{R*bbWBbH-16                                                     2.000 ± 0%
DecoderReuse/KJG8_@Dgx]_4V?xuyE%NRj-16                                                           2.000 ± 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                                 7.000 ± 0%
EncoderReuse/LFE.@D9F01_2%L%MIVD*9Goe-;WB-16                                                     3.000 ± 0%
EncoderReuse/LNAdApj[00aymkj[TKay9}ay-Sj[-16                                                     3.000 ± 0%
EncoderReuse/eaF#5R0#WBjYR+58-nWCWBn~bIsTbbayjFWof8jFj[WX-nNHR*jss.-16                           4.000 ± 0%
geomean                                                                                      ²                -77.80%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/bbrks/go-blurhash/base83
                 │ bench-v1.1.1.txt │          bench-v1.2.0.txt           │
                 │      sec/op      │   sec/op     vs base                │
Decode/3-16             8.936n ± 1%   2.064n ± 1%  -76.90% (p=0.000 n=10)
Decode/A-16             9.007n ± 2%   2.060n ± 2%  -77.13% (p=0.000 n=10)
Decode/:-16             8.999n ± 1%   2.072n ± 1%  -76.97% (p=0.000 n=10)
Decode/~-16             8.901n ± 0%   2.064n ± 2%  -76.81% (p=0.000 n=10)
Decode/01-16            8.889n ± 1%   2.060n ± 1%  -76.83% (p=0.000 n=10)
Decode/11-16            8.933n ± 1%   2.077n ± 1%  -76.76% (p=0.000 n=10)
Decode/33-16            8.921n ± 1%   2.083n ± 1%  -76.65% (p=0.000 n=10)
Decode/~$-16            8.838n ± 1%   2.096n ± 2%  -76.29% (p=0.000 n=10)
Decode/%%%%%%-16        8.835n ± 1%   2.072n ± 1%  -76.54% (p=0.000 n=10)
Encode/3-16            36.710n ± 1%   4.140n ± 4%  -88.72% (p=0.000 n=10)
Encode/A-16            37.020n ± 1%   4.200n ± 3%  -88.65% (p=0.000 n=10)
Encode/:-16            36.735n ± 1%   4.190n ± 4%  -88.59% (p=0.000 n=10)
Encode/~-16            36.975n ± 0%   4.124n ± 3%  -88.85% (p=0.000 n=10)
Encode/01-16           37.585n ± 2%   4.286n ± 4%  -88.60% (p=0.000 n=10)
Encode/11-16           37.055n ± 2%   4.214n ± 3%  -88.63% (p=0.000 n=10)
Encode/33-16           37.295n ± 1%   4.223n ± 3%  -88.68% (p=0.000 n=10)
Encode/~$-16           37.275n ± 1%   4.117n ± 3%  -88.96% (p=0.000 n=10)
Encode/%%%%%%-16       37.395n ± 1%   4.263n ± 1%  -88.60% (p=0.000 n=10)
geomean                 18.19n        2.948n       -83.79%

                 │ bench-v1.1.1.txt │            bench-v1.2.0.txt             │
                 │       B/op       │    B/op     vs base                     │
Decode/3-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/A-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/:-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/~-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/01-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/11-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/33-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/~$-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/%%%%%%-16       0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Encode/3-16            12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/A-16            12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/:-16            12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/~-16            12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/01-16           12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/11-16           12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/33-16           12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/~$-16           12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
Encode/%%%%%%-16       12.00 ± 0%      0.00 ± 0%  -100.00% (p=0.000 n=10)
geomean                           ²               ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

                 │ bench-v1.1.1.txt │            bench-v1.2.0.txt             │
                 │    allocs/op     │ allocs/op   vs base                     │
Decode/3-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/A-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/:-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/~-16            0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/01-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/11-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/33-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/~$-16           0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Decode/%%%%%%-16       0.000 ± 0%     0.000 ± 0%         ~ (p=1.000 n=10) ¹
Encode/3-16            3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/A-16            3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/:-16            3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/~-16            3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/01-16           3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/11-16           3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/33-16           3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/~$-16           3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
Encode/%%%%%%-16       3.000 ± 0%     0.000 ± 0%  -100.00% (p=0.000 n=10)
geomean                           ²               ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

Full Changelog: v1.1.1...v1.2.0