33package block
44
55import (
6- "bytes"
76 "encoding/binary"
7+ "sync"
88
99 "github.com/DataDog/zstd"
1010 "github.com/cockroachdb/errors"
1111)
1212
13+ type zstdCompressor struct {
14+ ctx zstd.Ctx
15+ }
16+
17+ var _ Compressor = (* zstdCompressor )(nil )
18+
19+ var zstdCompressorPool = sync.Pool {
20+ New : func () any {
21+ return & zstdCompressor {ctx : zstd .NewCtx ()}
22+ },
23+ }
24+
1325// UseStandardZstdLib indicates whether the zstd implementation is a port of the
1426// official one in the facebook/zstd repository.
1527//
@@ -26,21 +38,48 @@ const UseStandardZstdLib = true
2638// is sufficient. The subslice `compressedBuf[:varIntLen]` should already encode
2739// the length of `b` before calling Compress. It returns the encoded byte
2840// slice, including the `compressedBuf[:varIntLen]` prefix.
29- func (zstdCompressor ) Compress (compressedBuf []byte , b []byte ) (CompressionIndicator , []byte ) {
41+ func (z * zstdCompressor ) Compress (compressedBuf []byte , b []byte ) (CompressionIndicator , []byte ) {
3042 if len (compressedBuf ) < binary .MaxVarintLen64 {
3143 compressedBuf = append (compressedBuf , make ([]byte , binary .MaxVarintLen64 - len (compressedBuf ))... )
3244 }
45+
46+ // Get the bound and allocate the proper amount of memory instead of relying on
47+ // Datadog/zstd to do it for us. This allows us to avoid memcopying data around
48+ // for the varIntLen prefix.
49+ bound := zstd .CompressBound (len (b ))
50+ if cap (compressedBuf ) < binary .MaxVarintLen64 + bound {
51+ compressedBuf = make ([]byte , binary .MaxVarintLen64 , binary .MaxVarintLen64 + bound )
52+ }
53+
3354 varIntLen := binary .PutUvarint (compressedBuf , uint64 (len (b )))
34- buf := bytes .NewBuffer (compressedBuf [:varIntLen ])
35- writer := zstd .NewWriterLevel (buf , 3 )
36- writer .Write (b )
37- writer .Close ()
38- return ZstdCompressionIndicator , buf .Bytes ()
55+ result , err := z .ctx .CompressLevel (compressedBuf [varIntLen :varIntLen + bound ], b , 3 )
56+ if err != nil {
57+ panic ("Error while compressing using Zstd." )
58+ }
59+ if & result [0 ] != & compressedBuf [varIntLen ] {
60+ panic ("Allocated a new buffer despite checking CompressBound." )
61+ }
62+
63+ return ZstdCompressionIndicator , compressedBuf [:varIntLen + len (result )]
3964}
4065
66+ func (z * zstdCompressor ) Close () {
67+ zstdCompressorPool .Put (z )
68+ }
69+
70+ func getZstdCompressor () * zstdCompressor {
71+ return zstdCompressorPool .Get ().(* zstdCompressor )
72+ }
73+
74+ type zstdDecompressor struct {
75+ ctx zstd.Ctx
76+ }
77+
78+ var _ Decompressor = (* zstdDecompressor )(nil )
79+
4180// DecompressInto decompresses src with the Zstandard algorithm. The destination
42- // buffer must already be sufficiently sized, otherwise Decompress may error.
43- func (zstdDecompressor ) DecompressInto (dst , src []byte ) error {
81+ // buffer must already be sufficiently sized, otherwise DecompressInto may error.
82+ func (z * zstdDecompressor ) DecompressInto (dst , src []byte ) error {
4483 // The payload is prefixed with a varint encoding the length of
4584 // the decompressed block.
4685 _ , prefixLen := binary .Uvarint (src )
@@ -51,9 +90,23 @@ func (zstdDecompressor) DecompressInto(dst, src []byte) error {
5190 if len (dst ) == 0 {
5291 return errors .Errorf ("decodeZstd: empty dst buffer" )
5392 }
54- _ , err := zstd .DecompressInto (dst , src )
93+ _ , err := z . ctx .DecompressInto (dst , src )
5594 if err != nil {
5695 return err
5796 }
5897 return nil
5998}
99+
100+ func (z * zstdDecompressor ) Close () {
101+ zstdDecompressorPool .Put (z )
102+ }
103+
104+ var zstdDecompressorPool = sync.Pool {
105+ New : func () any {
106+ return & zstdDecompressor {ctx : zstd .NewCtx ()}
107+ },
108+ }
109+
110+ func getZstdDecompressor () * zstdDecompressor {
111+ return zstdDecompressorPool .Get ().(* zstdDecompressor )
112+ }
0 commit comments