@@ -26,48 +26,72 @@ type CompressionOpts struct {
26
26
UncompressedSize int32
27
27
}
28
28
29
- var zstdEncoders sync.Map // map[zstd.EncoderLevel]*zstd.Encoder
29
+ // mustZstdNewWriter creates a zstd.Encoder with the given level and a nil
30
+ // destination writer. It panics on any errors and should only be used at
31
+ // package initialization time.
32
+ func mustZstdNewWriter (lvl zstd.EncoderLevel ) * zstd.Encoder {
33
+ enc , err := zstd .NewWriter (nil , zstd .WithEncoderLevel (lvl ))
34
+ if err != nil {
35
+ panic (err )
36
+ }
37
+ return enc
38
+ }
39
+
40
+ var zstdEncoders = [zstd .SpeedBestCompression + 1 ]* zstd.Encoder {
41
+ 0 : nil , // zstd.speedNotSet
42
+ zstd .SpeedFastest : mustZstdNewWriter (zstd .SpeedFastest ),
43
+ zstd .SpeedDefault : mustZstdNewWriter (zstd .SpeedDefault ),
44
+ zstd .SpeedBetterCompression : mustZstdNewWriter (zstd .SpeedBetterCompression ),
45
+ zstd .SpeedBestCompression : mustZstdNewWriter (zstd .SpeedBestCompression ),
46
+ }
30
47
31
48
func getZstdEncoder (level zstd.EncoderLevel ) (* zstd.Encoder , error ) {
32
- if v , ok := zstdEncoders .Load (level ); ok {
33
- return v .(* zstd.Encoder ), nil
34
- }
35
- encoder , err := zstd .NewWriter (nil , zstd .WithEncoderLevel (level ))
36
- if err != nil {
37
- return nil , err
49
+ if zstd .SpeedFastest <= level && level <= zstd .SpeedBestCompression {
50
+ return zstdEncoders [level ], nil
38
51
}
39
- zstdEncoders . Store ( level , encoder )
40
- return encoder , nil
52
+ // The level is outside the expected range, return an error.
53
+ return nil , fmt . Errorf ( "invalid zstd compression level: %d" , level )
41
54
}
42
55
43
- var zlibEncoders sync.Map // map[int /*level*/]*zlibEncoder
56
+ // zlibEncodersOffset is the offset into the zlibEncoders array for a given
57
+ // compression level.
58
+ const zlibEncodersOffset = - zlib .HuffmanOnly // HuffmanOnly == -2
59
+
60
+ var zlibEncoders [zlib .BestCompression + zlibEncodersOffset + 1 ]sync.Pool
44
61
45
62
func getZlibEncoder (level int ) (* zlibEncoder , error ) {
46
- if v , ok := zlibEncoders .Load (level ); ok {
47
- return v .(* zlibEncoder ), nil
48
- }
49
- writer , err := zlib .NewWriterLevel (nil , level )
50
- if err != nil {
51
- return nil , err
63
+ if zlib .HuffmanOnly <= level && level <= zlib .BestCompression {
64
+ if enc , _ := zlibEncoders [level + zlibEncodersOffset ].Get ().(* zlibEncoder ); enc != nil {
65
+ return enc , nil
66
+ }
67
+ writer , err := zlib .NewWriterLevel (nil , level )
68
+ if err != nil {
69
+ return nil , err
70
+ }
71
+ enc := & zlibEncoder {writer : writer , level : level }
72
+ return enc , nil
52
73
}
53
- encoder := & zlibEncoder {writer : writer , buf : new (bytes.Buffer )}
54
- zlibEncoders .Store (level , encoder )
74
+ // The level is outside the expected range, return an error.
75
+ return nil , fmt .Errorf ("invalid zlib compression level: %d" , level )
76
+ }
55
77
56
- return encoder , nil
78
+ func putZlibEncoder (enc * zlibEncoder ) {
79
+ if enc != nil {
80
+ zlibEncoders [enc .level + zlibEncodersOffset ].Put (enc )
81
+ }
57
82
}
58
83
59
84
type zlibEncoder struct {
60
- mu sync.Mutex
61
85
writer * zlib.Writer
62
- buf * bytes.Buffer
86
+ buf bytes.Buffer
87
+ level int
63
88
}
64
89
65
90
func (e * zlibEncoder ) Encode (dst , src []byte ) ([]byte , error ) {
66
- e .mu .Lock ()
67
- defer e .mu .Unlock ()
91
+ defer putZlibEncoder (e )
68
92
69
93
e .buf .Reset ()
70
- e .writer .Reset (e .buf )
94
+ e .writer .Reset (& e .buf )
71
95
72
96
_ , err := e .writer .Write (src )
73
97
if err != nil {
@@ -105,8 +129,15 @@ func CompressPayload(in []byte, opts CompressionOpts) ([]byte, error) {
105
129
}
106
130
}
107
131
132
+ var zstdReaderPool = sync.Pool {
133
+ New : func () interface {} {
134
+ r , _ := zstd .NewReader (nil )
135
+ return r
136
+ },
137
+ }
138
+
108
139
// DecompressPayload takes a byte slice that has been compressed and undoes it according to the options passed
109
- func DecompressPayload (in []byte , opts CompressionOpts ) (uncompressed []byte , err error ) {
140
+ func DecompressPayload (in []byte , opts CompressionOpts ) ([]byte , error ) {
110
141
switch opts .Compressor {
111
142
case wiremessage .CompressorNoOp :
112
143
return in , nil
@@ -117,34 +148,29 @@ func DecompressPayload(in []byte, opts CompressionOpts) (uncompressed []byte, er
117
148
} else if int32 (l ) != opts .UncompressedSize {
118
149
return nil , fmt .Errorf ("unexpected decompression size, expected %v but got %v" , opts .UncompressedSize , l )
119
150
}
120
- uncompressed = make ([]byte , opts .UncompressedSize )
121
- return snappy .Decode (uncompressed , in )
151
+ out : = make ([]byte , opts .UncompressedSize )
152
+ return snappy .Decode (out , in )
122
153
case wiremessage .CompressorZLib :
123
154
r , err := zlib .NewReader (bytes .NewReader (in ))
124
155
if err != nil {
125
156
return nil , err
126
157
}
127
- defer func () {
128
- err = r .Close ()
129
- }()
130
- uncompressed = make ([]byte , opts .UncompressedSize )
131
- _ , err = io .ReadFull (r , uncompressed )
132
- if err != nil {
158
+ out := make ([]byte , opts .UncompressedSize )
159
+ if _ , err := io .ReadFull (r , out ); err != nil {
133
160
return nil , err
134
161
}
135
- return uncompressed , nil
136
- case wiremessage .CompressorZstd :
137
- r , err := zstd .NewReader (bytes .NewBuffer (in ))
138
- if err != nil {
139
- return nil , err
140
- }
141
- defer r .Close ()
142
- uncompressed = make ([]byte , opts .UncompressedSize )
143
- _ , err = io .ReadFull (r , uncompressed )
144
- if err != nil {
162
+ if err := r .Close (); err != nil {
145
163
return nil , err
146
164
}
147
- return uncompressed , nil
165
+ return out , nil
166
+ case wiremessage .CompressorZstd :
167
+ buf := make ([]byte , 0 , opts .UncompressedSize )
168
+ // Using a pool here is about ~20% faster
169
+ // than using a single global zstd.Reader
170
+ r := zstdReaderPool .Get ().(* zstd.Decoder )
171
+ out , err := r .DecodeAll (in , buf )
172
+ zstdReaderPool .Put (r )
173
+ return out , err
148
174
default :
149
175
return nil , fmt .Errorf ("unknown compressor ID %v" , opts .Compressor )
150
176
}
0 commit comments