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

encoding: eliminate unnecessary allocations #5655

Merged
merged 3 commits into from
Aug 29, 2023
Merged
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
5 changes: 3 additions & 2 deletions crypto/merklesignature/persistentMerkleSignatureScheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ func (s *Secrets) Persist(store db.Accessor) error {
return fmt.Errorf("Secrets.Persist: %w", ErrKeyLifetimeIsZero)
}
round := indexToRound(s.FirstValid, s.KeyLifetime, 0)
encodedKey := protocol.GetEncodingBuf()
encodedBuf := protocol.GetEncodingBuf()
encodedKey := encodedBuf.Bytes()
err := store.Atomic(func(ctx context.Context, tx *sql.Tx) error {
err := InstallStateProofTable(tx) // assumes schema table already exists (created by partInstallDatabase)
if err != nil {
Expand Down Expand Up @@ -126,7 +127,7 @@ func (s *Secrets) Persist(store db.Accessor) error {

return nil
})
protocol.PutEncodingBuf(encodedKey)
protocol.PutEncodingBuf(encodedBuf.Update(encodedKey))
if err != nil {
return fmt.Errorf("Secrets.Persist: %w", err)
}
Expand Down
21 changes: 12 additions & 9 deletions data/transactions/signedtxn.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,17 @@

// GetEncodedLength returns the length in bytes of the encoded transaction
func (s SignedTxn) GetEncodedLength() int {
enc := s.MarshalMsg(protocol.GetEncodingBuf())
defer protocol.PutEncodingBuf(enc)
buf := protocol.GetEncodingBuf()
enc := s.MarshalMsg(buf.Bytes())
defer protocol.PutEncodingBuf(buf.Update(enc))
return len(enc)
}

// GetEncodedLength returns the length in bytes of the encoded transaction
func (s SignedTxnInBlock) GetEncodedLength() int {
enc := s.MarshalMsg(protocol.GetEncodingBuf())
defer protocol.PutEncodingBuf(enc)
buf := protocol.GetEncodingBuf()
enc := s.MarshalMsg(buf.Bytes())
defer protocol.PutEncodingBuf(buf.Update(enc))

Check warning on line 84 in data/transactions/signedtxn.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/signedtxn.go#L82-L84

Added lines #L82 - L84 were not covered by tests
return len(enc)
}

Expand Down Expand Up @@ -116,16 +118,17 @@

// Hash implements an optimized version of crypto.HashObj(s).
func (s *SignedTxnInBlock) Hash() crypto.Digest {
enc := s.MarshalMsg(append(protocol.GetEncodingBuf(), []byte(protocol.SignedTxnInBlock)...))
defer protocol.PutEncodingBuf(enc)

buf := protocol.GetEncodingBuf()
enc := s.MarshalMsg(append(buf.Bytes(), []byte(protocol.SignedTxnInBlock)...))
defer protocol.PutEncodingBuf(buf.Update(enc))
return crypto.Hash(enc)
}

// HashSHA256 implements an optimized version of crypto.HashObj(s) using SHA256 instead of the default SHA512_256.
func (s *SignedTxnInBlock) HashSHA256() crypto.Digest {
enc := s.MarshalMsg(append(protocol.GetEncodingBuf(), []byte(protocol.SignedTxnInBlock)...))
defer protocol.PutEncodingBuf(enc)
buf := protocol.GetEncodingBuf()
enc := s.MarshalMsg(append(buf.Bytes(), []byte(protocol.SignedTxnInBlock)...))
defer protocol.PutEncodingBuf(buf.Update(enc))

Check warning on line 131 in data/transactions/signedtxn.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/signedtxn.go#L129-L131

Added lines #L129 - L131 were not covered by tests

return sha256.Sum256(enc)
}
Expand Down
12 changes: 8 additions & 4 deletions data/transactions/teal.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,13 @@ func (ed EvalDelta) Equal(o EvalDelta) bool {
// tedious) field comparisons. == is not defined on almost any of the
// subfields because of slices.
func (stx SignedTxn) equal(o SignedTxn) bool {
stxenc := stx.MarshalMsg(protocol.GetEncodingBuf())
defer protocol.PutEncodingBuf(stxenc)
oenc := o.MarshalMsg(protocol.GetEncodingBuf())
defer protocol.PutEncodingBuf(oenc)
buf1 := protocol.GetEncodingBuf()
stxenc := stx.MarshalMsg(buf1.Bytes())
defer protocol.PutEncodingBuf(buf1.Update(stxenc))

buf2 := protocol.GetEncodingBuf()
oenc := o.MarshalMsg(buf2.Bytes())
defer protocol.PutEncodingBuf(buf2.Update(oenc))

return bytes.Equal(stxenc, oenc)
}
71 changes: 61 additions & 10 deletions data/transactions/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"encoding/binary"
"errors"
"fmt"
"sync"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
Expand Down Expand Up @@ -177,31 +178,81 @@
return protocol.Transaction, protocol.Encode(&tx)
}

// txAllocSize returns the max possible size of a transaction without state proof fields.
// It is used to preallocate a buffer for encoding a transaction.
func txAllocSize() int {
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
return TransactionMaxSize() - StateProofTxnFieldsMaxSize()
}

// txEncodingPool holds temporary byte slice buffers used for encoding transaction messages.
// Note, it prepends protocol.Transaction tag to the buffer economizing on subsequent append ops.
var txEncodingPool = sync.Pool{
New: func() interface{} {
size := txAllocSize() + len(protocol.Transaction)
buf := make([]byte, len(protocol.Transaction), size)
copy(buf, []byte(protocol.Transaction))
return &txEncodingBuf{b: buf}
},
}

// getTxEncodingBuf returns a wrapped byte slice that can be used for encoding a
// temporary message. The byte slice length of encoded Transaction{} object.
// The caller gets full ownership of the byte slice,
// but is encouraged to return it using putEncodingBuf().
func getTxEncodingBuf() *txEncodingBuf {
buf := txEncodingPool.Get().(*txEncodingBuf)
return buf
}

// putTxEncodingBuf places a byte slice into the pool of temporary buffers
// for encoding. The caller gives up ownership of the byte slice when
// passing it to putTxEncodingBuf().
func putTxEncodingBuf(buf *txEncodingBuf) {
buf.b = buf.b[:len(protocol.Transaction)]
txEncodingPool.Put(buf)
}

type txEncodingBuf struct {
b []byte
}

// ID returns the Txid (i.e., hash) of the transaction.
func (tx Transaction) ID() Txid {
enc := tx.MarshalMsg(append(protocol.GetEncodingBuf(), []byte(protocol.Transaction)...))
defer protocol.PutEncodingBuf(enc)
buf := getTxEncodingBuf()
enc := tx.MarshalMsg(buf.b)
if cap(enc) > cap(buf.b) {
// use a bigger buffer as New's estimate was too small
buf.b = enc

Check warning on line 225 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L225

Added line #L225 was not covered by tests
}
defer putTxEncodingBuf(buf)
return Txid(crypto.Hash(enc))
}

// IDSha256 returns the digest (i.e., hash) of the transaction.
// This is different from the canonical ID computed with Sum512_256 hashing function.
func (tx Transaction) IDSha256() crypto.Digest {
enc := tx.MarshalMsg(append(protocol.GetEncodingBuf(), []byte(protocol.Transaction)...))
defer protocol.PutEncodingBuf(enc)
buf := getTxEncodingBuf()
enc := tx.MarshalMsg(buf.b)
if cap(enc) > cap(buf.b) {
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
buf.b = enc

Check warning on line 237 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L234-L237

Added lines #L234 - L237 were not covered by tests
}
defer putTxEncodingBuf(buf)

Check warning on line 239 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L239

Added line #L239 was not covered by tests
return sha256.Sum256(enc)
}

// InnerID returns something akin to Txid, but folds in the parent Txid and the
// index of the inner call.
func (tx Transaction) InnerID(parent Txid, index int) Txid {
input := append(protocol.GetEncodingBuf(), []byte(protocol.Transaction)...)
input = append(input, parent[:]...)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(index))
input = append(input, buf...)
buf := getTxEncodingBuf()
input := append(buf.b, parent[:]...)
var indexBuf [8]byte
binary.BigEndian.PutUint64(indexBuf[:], uint64(index))
input = append(input, indexBuf[:]...)

Check warning on line 250 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L246-L250

Added lines #L246 - L250 were not covered by tests
enc := tx.MarshalMsg(input)
defer protocol.PutEncodingBuf(enc)
if cap(enc) > cap(buf.b) {
buf.b = enc

Check warning on line 253 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L252-L253

Added lines #L252 - L253 were not covered by tests
}
defer putTxEncodingBuf(buf)

Check warning on line 255 in data/transactions/transaction.go

View check run for this annotation

Codecov / codecov/patch

data/transactions/transaction.go#L255

Added line #L255 was not covered by tests
return Txid(crypto.Hash(enc))
}

Expand Down
30 changes: 25 additions & 5 deletions protocol/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,21 +288,41 @@
// encodingPool holds temporary byte slice buffers used for encoding messages.
var encodingPool = sync.Pool{
New: func() interface{} {
return []byte{}
return &EncodingBuf{b: make([]byte, 0)}

Check warning on line 291 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L291

Added line #L291 was not covered by tests
},
}

// EncodingBuf is a wrapper for a byte slice that can be used for encoding
type EncodingBuf struct {
b []byte
}

// Bytes returns the underlying byte slice
func (eb *EncodingBuf) Bytes() []byte {
return eb.b

Check warning on line 302 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L301-L302

Added lines #L301 - L302 were not covered by tests
}

// Update updates the underlying byte slice to the given one if its capacity exceeds the current one.
func (eb *EncodingBuf) Update(v []byte) *EncodingBuf {
if cap(eb.b) < cap(v) {
eb.b = v

Check warning on line 308 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L306-L308

Added lines #L306 - L308 were not covered by tests
}
return eb

Check warning on line 310 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L310

Added line #L310 was not covered by tests
}

// GetEncodingBuf returns a byte slice that can be used for encoding a
// temporary message. The byte slice has zero length but potentially
// non-zero capacity. The caller gets full ownership of the byte slice,
// but is encouraged to return it using PutEncodingBuf().
func GetEncodingBuf() []byte {
return encodingPool.Get().([]byte)[:0]
func GetEncodingBuf() *EncodingBuf {
buf := encodingPool.Get().(*EncodingBuf)
buf.b = buf.b[:0]
return buf

Check warning on line 320 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L317-L320

Added lines #L317 - L320 were not covered by tests
}

// PutEncodingBuf places a byte slice into the pool of temporary buffers
// for encoding. The caller gives up ownership of the byte slice when
// passing it to PutEncodingBuf().
func PutEncodingBuf(s []byte) {
encodingPool.Put(s)
func PutEncodingBuf(buf *EncodingBuf) {
encodingPool.Put(buf)

Check warning on line 327 in protocol/codec.go

View check run for this annotation

Codecov / codecov/patch

protocol/codec.go#L326-L327

Added lines #L326 - L327 were not covered by tests
}