Skip to content

Commit

Permalink
codec: add package for codec pooling
Browse files Browse the repository at this point in the history
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Mar 10, 2021
1 parent 9962202 commit 1fb6dcf
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/rs/zerolog v1.20.0
github.com/streadway/amqp v1.0.0
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/ugorji/go/codec v1.2.4
github.com/urfave/cli/v2 v2.2.0
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.16.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.16.0
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,12 @@ github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/ugorji/go v1.2.4 h1:cTciPbZ/VSOzCLKclmssnfQ/jyoVyOcJ3aoJyUV1Urc=
github.com/ugorji/go v1.2.4/go.mod h1:EuaSCk8iZMdIspsu6HXH7X2UGKw1ezO4wCfGszGmmo4=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.4 h1:C5VurWRRCKjuENsbM6GYVw8W++WVW9rSxoACKIvxzz8=
github.com/ugorji/go/codec v1.2.4/go.mod h1:bWBu1+kIRWcF8uMklKaJrR6fTWQOwAlrIzX22pHwryA=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
Expand Down
64 changes: 64 additions & 0 deletions internal/codec/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Package codec is a unified place for configuring and allocating JSON encoders
// and decoders.
package codec

import (
"io"
"sync"

"github.com/ugorji/go/codec"
)

var jsonHandle codec.JsonHandle

func init() {
// This is documented to cause "smart buffering".
jsonHandle.WriterBufferSize = 4096
jsonHandle.ReaderBufferSize = 4096
}

// Encoder and decoder pools, to reuse if possible.
var (
encPool = sync.Pool{
New: func() interface{} {
return codec.NewEncoder(nil, &jsonHandle)
},
}
decPool = sync.Pool{
New: func() interface{} {
return codec.NewDecoder(nil, &jsonHandle)
},
}
)

// Encoder encodes.
type Encoder = codec.Encoder

// GetEncoder returns an encoder configured to write to w.
func GetEncoder(w io.Writer) *Encoder {
e := encPool.Get().(*Encoder)
e.Reset(w)
return e
}

// PutEncoder returns an encoder to the pool.
func PutEncoder(e *Encoder) {
e.Reset(nil)
encPool.Put(e)
}

// Decoder decodes.
type Decoder = codec.Decoder

// GetDecoder returns a decoder configured to read from r.
func GetDecoder(r io.Reader) *Decoder {
d := decPool.Get().(*Decoder)
d.Reset(r)
return d
}

// PutDecoder returns a decoder to the pool.
func PutDecoder(d *Decoder) {
d.Reset(nil)
decPool.Put(d)
}
74 changes: 74 additions & 0 deletions internal/codec/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package codec

import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
)

func Example() {
enc := GetEncoder(os.Stdout)
defer PutEncoder(enc)
enc.MustEncode([]string{"a", "slice", "of", "strings"})
fmt.Fprintln(os.Stdout)
enc.MustEncode(nil)
fmt.Fprintln(os.Stdout)
enc.MustEncode(map[string]string{})
fmt.Fprintln(os.Stdout)
// Output: ["a","slice","of","strings"]
// null
// {}
}

func BenchmarkDecode(b *testing.B) {
b.ReportAllocs()
want := map[string]string{
"a": strings.Repeat(`A`, 2048),
"b": strings.Repeat(`B`, 2048),
"c": strings.Repeat(`C`, 2048),
"d": strings.Repeat(`D`, 2048),
}
got := make(map[string]string, len(want))
b.ResetTimer()

for i := 0; i < b.N; i++ {
dec := GetDecoder(JSONReader(want))
err := dec.Decode(&got)
PutDecoder(dec)
if err != nil {
b.Error(err)
}
if !cmp.Equal(got, want) {
b.Error(cmp.Diff(got, want))
}
}
}

func BenchmarkDecodeStdlib(b *testing.B) {
b.ReportAllocs()
want := map[string]string{
"a": strings.Repeat(`A`, 2048),
"b": strings.Repeat(`B`, 2048),
"c": strings.Repeat(`C`, 2048),
"d": strings.Repeat(`D`, 2048),
}
got := make(map[string]string, len(want))
b.ResetTimer()

for i := 0; i < b.N; i++ {
x, err := json.Marshal(want)
if err != nil {
b.Error(err)
}
if err := json.Unmarshal(x, &got); err != nil {
b.Error(err)
}
if !cmp.Equal(got, want) {
b.Error(cmp.Diff(got, want))
}
}
}
20 changes: 20 additions & 0 deletions internal/codec/reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package codec

import "io"

// JSONReader returns an io.ReadCloser backed by a pipe being fed by a JSON
// encoder.
func JSONReader(v interface{}) io.ReadCloser {
r, w := io.Pipe()
// This unsupervised goroutine should be fine, because the writer will error
// once the reader is closed.
go func() {
enc := GetEncoder(w)
defer PutEncoder(enc)
defer w.Close()
if err := enc.Encode(v); err != nil {
w.CloseWithError(err)
}
}()
return r
}

0 comments on commit 1fb6dcf

Please sign in to comment.