Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(tm2/pkg/amino): reduce RAM heavy-handedness by *bytes.Buffer pooled reuse #3489

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require (
github.com/sig-0/insertion-queue v0.0.0-20241004125609-6b3ca841346b
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/valyala/bytebufferpool v1.0.0
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.etcd.io/bbolt v1.3.11
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 49 additions & 16 deletions tm2/pkg/amino/amino.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) {
cdc.doAutoseal()

// Write the bytes here.
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

// Write the bz without length-prefixing.
bz, err := cdc.Marshal(o)
Expand All @@ -239,7 +243,7 @@ func (cdc *Codec) MarshalSized(o interface{}) ([]byte, error) {
return nil, err
}

return buf.Bytes(), nil
return copyBytes(buf.Bytes()), nil
}

// MarshalSizedWriter writes the bytes as would be returned from
Expand Down Expand Up @@ -271,7 +275,11 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) {
cdc.doAutoseal()

// Write the bytes here.
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

// Write the bz without length-prefixing.
bz, err := cdc.MarshalAny(o)
Expand All @@ -291,7 +299,7 @@ func (cdc *Codec) MarshalAnySized(o interface{}) ([]byte, error) {
return nil, err
}

return buf.Bytes(), nil
return copyBytes(buf.Bytes()), nil
}

func (cdc *Codec) MustMarshalAnySized(o interface{}) []byte {
Expand Down Expand Up @@ -357,7 +365,12 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) {

// Encode Amino:binary bytes.
var bz []byte
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

rt := rv.Type()
info, err := cdc.getTypeInfoWLock(rt)
if err != nil {
Expand All @@ -377,7 +390,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) {
if err = cdc.writeFieldIfNotEmpty(buf, 1, info, FieldOptions{}, FieldOptions{}, rv, writeEmpty); err != nil {
return nil, err
}
bz = buf.Bytes()
bz = copyBytes(buf.Bytes())
} else {
// The passed in BinFieldNum is only relevant for when the type is to
// be encoded unpacked (elements are Typ3_ByteLength). In that case,
Expand All @@ -387,7 +400,7 @@ func (cdc *Codec) MarshalReflect(o interface{}) ([]byte, error) {
if err != nil {
return nil, err
}
bz = buf.Bytes()
bz = copyBytes(buf.Bytes())
}
// If bz is empty, prefer nil.
if len(bz) == 0 {
Expand Down Expand Up @@ -443,16 +456,26 @@ func (cdc *Codec) MarshalAny(o interface{}) ([]byte, error) {
}

// Encode as interface.
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()
err = cdc.encodeReflectBinaryInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{}, true)
if err != nil {
return nil, err
}
bz := buf.Bytes()
bz := copyBytes(buf.Bytes())

return bz, nil
}

func copyBytes(bz []byte) []byte {
cp := make([]byte, len(bz))
copy(cp, bz)
return cp
}

// Panics if error.
func (cdc *Codec) MustMarshalAny(o interface{}) []byte {
bz, err := cdc.MarshalAny(o)
Expand Down Expand Up @@ -764,15 +787,20 @@ func (cdc *Codec) JSONMarshal(o interface{}) ([]byte, error) {
return []byte("null"), nil
}
rt := rv.Type()
w := new(bytes.Buffer)
w := poolBytesBuffer.Get()
defer func() {
w.Reset()
poolBytesBuffer.Put(w)
}()
info, err := cdc.getTypeInfoWLock(rt)
if err != nil {
return nil, err
}
if err = cdc.encodeReflectJSON(w, info, rv, FieldOptions{}); err != nil {
return nil, err
}
return w.Bytes(), nil

return copyBytes(w.Bytes()), nil
}

func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) {
Expand Down Expand Up @@ -802,12 +830,17 @@ func (cdc *Codec) MarshalJSONAny(o interface{}) ([]byte, error) {
}

// Encode as interface.
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

err = cdc.encodeReflectJSONInterface(buf, iinfo, reflect.ValueOf(&ivar).Elem(), FieldOptions{})
if err != nil {
return nil, err
}
bz := buf.Bytes()
bz := copyBytes(buf.Bytes())

return bz, nil
}
Expand Down Expand Up @@ -863,12 +896,12 @@ func (cdc *Codec) MarshalJSONIndent(o interface{}, prefix, indent string) ([]byt
if err != nil {
return nil, err
}

var out bytes.Buffer
err = json.Indent(&out, bz, prefix, indent)
if err != nil {
if err := json.Indent(&out, bz, prefix, indent); err != nil {
return nil, err
}
return out.Bytes(), nil
return copyBytes(out.Bytes()), nil
}

// ----------------------------------------
Expand Down
52 changes: 44 additions & 8 deletions tm2/pkg/amino/binary_encode.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package amino

import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"reflect"

"github.com/valyala/bytebufferpool"
)

const beOptionByte = 0x01
Expand Down Expand Up @@ -209,6 +210,8 @@
return err
}

var poolBytesBuffer = new(bytebufferpool.Pool)

func (cdc *Codec) encodeReflectBinaryInterface(w io.Writer, iinfo *TypeInfo, rv reflect.Value,
fopts FieldOptions, bare bool,
) (err error) {
Expand Down Expand Up @@ -250,7 +253,12 @@

// For Proto3 compatibility, encode interfaces as google.protobuf.Any
// Write field #1, TypeURL
buf := bytes.NewBuffer(nil)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

{
fnum := uint32(1)
err = encodeFieldNumberAndTyp3(buf, fnum, Typ3ByteLength)
Expand All @@ -269,7 +277,12 @@
{
// google.protobuf.Any values must be a struct, or an unpacked list which
// is indistinguishable from a struct.
buf2 := bytes.NewBuffer(nil)
buf2 := poolBytesBuffer.Get()
defer func() {
buf2.Reset()
poolBytesBuffer.Put(buf2)
}()

if !cinfo.IsStructOrUnpacked(fopts) {
writeEmpty := false
// Encode with an implicit struct, with a single field with number 1.
Expand Down Expand Up @@ -356,7 +369,11 @@

// Proto3 byte-length prefixing incurs alloc cost on the encoder.
// Here we incur it for unpacked form for ease of dev.
buf := bytes.NewBuffer(nil)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

// If elem is not already a ByteLength type, write in packed form.
// This is a Proto wart due to Proto backwards compatibility issues.
Expand Down Expand Up @@ -431,20 +448,28 @@
// form) are represented as lists of implicit structs.
if writeImplicit {
// Write field key for Value field of implicit struct.
buf2 := new(bytes.Buffer)
buf2 := poolBytesBuffer.Get()
buf2Done := func() {
buf2.Reset()
poolBytesBuffer.Put(buf2)
}

err = encodeFieldNumberAndTyp3(buf2, 1, Typ3ByteLength)
if err != nil {
buf2Done()

Check warning on line 459 in tm2/pkg/amino/binary_encode.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/amino/binary_encode.go#L459

Added line #L459 was not covered by tests
return
}
// Write field value of implicit struct to buf2.
efopts := fopts
efopts.BinFieldNum = 0 // dontcare
err = cdc.encodeReflectBinary(buf2, einfo, derv, efopts, false, 0)
if err != nil {
buf2Done()

Check warning on line 467 in tm2/pkg/amino/binary_encode.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/amino/binary_encode.go#L467

Added line #L467 was not covered by tests
return
}
// Write implicit struct to buf.
err = EncodeByteSlice(buf, buf2.Bytes())
buf2Done()
if err != nil {
return
}
Expand Down Expand Up @@ -497,7 +522,11 @@

// Proto3 incurs a cost in writing non-root structs.
// Here we incur it for root structs as well for ease of dev.
buf := bytes.NewBuffer(nil)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

for _, field := range info.Fields {
// Get type info for field.
Expand Down Expand Up @@ -552,8 +581,15 @@
return
}

type bufLike interface {
io.Writer
Len() int
Bytes() []byte
Set([]byte)
}

func (cdc *Codec) writeFieldIfNotEmpty(
buf *bytes.Buffer,
buf bufLike,
fieldNum uint32,
finfo *TypeInfo,
structsFopts FieldOptions, // the wrapping struct's FieldOptions if any
Expand All @@ -579,7 +615,7 @@
if !isWriteEmpty && lBeforeValue == lAfterValue-1 && buf.Bytes()[buf.Len()-1] == 0x00 {
// rollback typ3/fieldnum and last byte if
// not a pointer and empty:
buf.Truncate(lBeforeKey)
buf.Set(buf.Bytes()[:lBeforeKey])
}
return nil
}
Expand Down
8 changes: 6 additions & 2 deletions tm2/pkg/amino/codec.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package amino

import (
"bytes"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -113,7 +112,12 @@
// before it's fully populated.
return "<new TypeInfo>"
}
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

Check warning on line 119 in tm2/pkg/amino/codec.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/amino/codec.go#L115-L119

Added lines #L115 - L119 were not covered by tests

buf.Write([]byte("TypeInfo{"))
buf.Write([]byte(fmt.Sprintf("Type:%v,", info.Type)))
if info.ConcreteInfo.Registered {
Expand Down
8 changes: 6 additions & 2 deletions tm2/pkg/amino/json_encode.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package amino

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -156,7 +155,12 @@ func (cdc *Codec) encodeReflectJSONInterface(w io.Writer, iinfo *TypeInfo, rv re
}

// Write Value to buffer
buf := new(bytes.Buffer)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

cdc.encodeReflectJSON(buf, cinfo, crv, fopts)
value := buf.Bytes()
if len(value) == 0 {
Expand Down
8 changes: 6 additions & 2 deletions tm2/pkg/amino/wellknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package amino
// NOTE: We must not depend on protubuf libraries for serialization.

import (
"bytes"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -342,7 +341,12 @@ func encodeReflectBinaryWellKnown(w io.Writer, info *TypeInfo, rv reflect.Value,
}
// Maybe recurse with length-prefixing.
if !bare {
buf := bytes.NewBuffer(nil)
buf := poolBytesBuffer.Get()
defer func() {
buf.Reset()
poolBytesBuffer.Put(buf)
}()

ok, err = encodeReflectBinaryWellKnown(buf, info, rv, fopts, true)
if err != nil {
return false, err
Expand Down
Loading