From 620295efe3aedfb88234dcbbbee10290a1e731a4 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 6 Aug 2021 10:22:06 +0300 Subject: [PATCH 1/5] transaction: add benchmark for transaction serialization Signed-off-by: Evgeniy Stratonikov --- pkg/core/transaction/bench_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/core/transaction/bench_test.go b/pkg/core/transaction/bench_test.go index 991d729dc..763637680 100644 --- a/pkg/core/transaction/bench_test.go +++ b/pkg/core/transaction/bench_test.go @@ -64,3 +64,14 @@ func BenchmarkTransaction_Bytes(b *testing.B) { _ = tx.Bytes() } } + +func BenchmarkGetVarSize(b *testing.B) { + tx, err := NewTransactionFromBytes(benchTx) + require.NoError(b, err) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _ = io.GetVarSize(tx) + } +} From c69670c85bf1af1576b1eb93c6eb1adf79c9392a Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 6 Aug 2021 10:45:27 +0300 Subject: [PATCH 2/5] io: use a single slice for numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slice takes 24 bytes of memory, while we really need only 9. ``` name old time/op new time/op delta Transaction_Bytes-8 667ns ±17% 583ns ± 6% -12.50% (p=0.000 n=10+10) GetVarSize-8 283ns ±11% 189ns ± 5% -33.37% (p=0.000 n=10+10) name old alloc/op new alloc/op delta Transaction_Bytes-8 1.01kB ± 0% 0.88kB ± 0% -12.70% (p=0.000 n=10+10) GetVarSize-8 184B ± 0% 56B ± 0% -69.57% (p=0.000 n=10+10) name old allocs/op new allocs/op delta Transaction_Bytes-8 7.00 ± 0% 6.00 ± 0% -14.29% (p=0.000 n=10+10) GetVarSize-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.000 n=10+10) ``` Signed-off-by: Evgeniy Stratonikov --- pkg/io/binaryWriter.go | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/pkg/io/binaryWriter.go b/pkg/io/binaryWriter.go index 5ec3e42fa..46915725e 100644 --- a/pkg/io/binaryWriter.go +++ b/pkg/io/binaryWriter.go @@ -11,56 +11,47 @@ import ( // from a struct with many fields. type BinWriter struct { w io.Writer - uv []byte - u64 []byte - u32 []byte - u16 []byte - u8 []byte Err error + uv [9]byte } // NewBinWriterFromIO makes a BinWriter from io.Writer. func NewBinWriterFromIO(iow io.Writer) *BinWriter { - uv := make([]byte, 9) - u64 := uv[:8] - u32 := u64[:4] - u16 := u64[:2] - u8 := u64[:1] - return &BinWriter{w: iow, uv: uv, u64: u64, u32: u32, u16: u16, u8: u8} + return &BinWriter{w: iow} } // WriteU64LE writes an uint64 value into the underlying io.Writer in // little-endian format. func (w *BinWriter) WriteU64LE(u64 uint64) { - binary.LittleEndian.PutUint64(w.u64, u64) - w.WriteBytes(w.u64) + binary.LittleEndian.PutUint64(w.uv[:8], u64) + w.WriteBytes(w.uv[:8]) } // WriteU32LE writes an uint32 value into the underlying io.Writer in // little-endian format. func (w *BinWriter) WriteU32LE(u32 uint32) { - binary.LittleEndian.PutUint32(w.u32, u32) - w.WriteBytes(w.u32) + binary.LittleEndian.PutUint32(w.uv[:4], u32) + w.WriteBytes(w.uv[:4]) } // WriteU16LE writes an uint16 value into the underlying io.Writer in // little-endian format. func (w *BinWriter) WriteU16LE(u16 uint16) { - binary.LittleEndian.PutUint16(w.u16, u16) - w.WriteBytes(w.u16) + binary.LittleEndian.PutUint16(w.uv[:2], u16) + w.WriteBytes(w.uv[:2]) } // WriteU16BE writes an uint16 value into the underlying io.Writer in // big-endian format. func (w *BinWriter) WriteU16BE(u16 uint16) { - binary.BigEndian.PutUint16(w.u16, u16) - w.WriteBytes(w.u16) + binary.BigEndian.PutUint16(w.uv[:2], u16) + w.WriteBytes(w.uv[:2]) } // WriteB writes a byte into the underlying io.Writer. func (w *BinWriter) WriteB(u8 byte) { - w.u8[0] = u8 - w.WriteBytes(w.u8) + w.uv[0] = u8 + w.WriteBytes(w.uv[:1]) } // WriteBool writes a boolean value into the underlying io.Writer encoded as @@ -108,7 +99,7 @@ func (w *BinWriter) WriteVarUint(val uint64) { return } - n := PutVarUint(w.uv, val) + n := PutVarUint(w.uv[:], val) w.WriteBytes(w.uv[:n]) } From dacf025dd90320a14ab859a3d5a28e376e0c9052 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 6 Aug 2021 10:52:24 +0300 Subject: [PATCH 3/5] io: add `Grow` to `BinWriter` Signed-off-by: Evgeniy Stratonikov --- pkg/io/binaryWriter.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/io/binaryWriter.go b/pkg/io/binaryWriter.go index 46915725e..5f234c1b4 100644 --- a/pkg/io/binaryWriter.go +++ b/pkg/io/binaryWriter.go @@ -1,6 +1,7 @@ package io import ( + "bytes" "encoding/binary" "io" "reflect" @@ -144,3 +145,11 @@ func (w *BinWriter) WriteVarBytes(b []byte) { func (w *BinWriter) WriteString(s string) { w.WriteVarBytes([]byte(s)) } + +// Grow tries to increase underlying buffer capacity so that at least n bytes +// can be written without reallocation. If the writer is not a buffer, this is a no-op. +func (w *BinWriter) Grow(n int) { + if b, ok := w.w.(*bytes.Buffer); ok { + b.Grow(n) + } +} From c74de9a5797c7a734daebb52b58b1451863d8080 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 6 Aug 2021 11:15:50 +0300 Subject: [PATCH 4/5] network: preallocate buffer for message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta MessageBytes-8 740ns ± 0% 684ns ± 2% -7.58% (p=0.000 n=10+10) name old alloc/op new alloc/op delta MessageBytes-8 1.39kB ± 0% 1.20kB ± 0% -13.79% (p=0.000 n=10+10) name old allocs/op new allocs/op delta MessageBytes-8 11.0 ± 0% 10.0 ± 0% -9.09% (p=0.000 n=10+10) ``` Signed-off-by: Evgeniy Stratonikov --- pkg/network/message.go | 5 +++++ pkg/network/message_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pkg/network/message.go b/pkg/network/message.go index 5eb360d9d..cbdaa554f 100644 --- a/pkg/network/message.go +++ b/pkg/network/message.go @@ -182,6 +182,11 @@ func (m *Message) Encode(br *io.BinWriter) error { if err := m.tryCompressPayload(); err != nil { return err } + growSize := 2 + 1 // header + empty payload + if m.compressedPayload != nil { + growSize += 8 + len(m.compressedPayload) // varint + byte-slice + } + br.Grow(growSize) br.WriteB(byte(m.Flags)) br.WriteB(byte(m.Command)) if m.compressedPayload != nil { diff --git a/pkg/network/message_test.go b/pkg/network/message_test.go index 879d4a9f7..c4976a702 100644 --- a/pkg/network/message_test.go +++ b/pkg/network/message_test.go @@ -52,6 +52,34 @@ func TestEncodeDecodeVersion(t *testing.T) { require.NotEqual(t, len(expected.compressedPayload), len(uncompressed)) } +func BenchmarkMessageBytes(b *testing.B) { + // shouldn't try to compress headers payload + ep := &payload.Extensible{ + Category: "consensus", + ValidBlockStart: rand.Uint32(), + ValidBlockEnd: rand.Uint32(), + Sender: util.Uint160{}, + Data: make([]byte, 300), + Witness: transaction.Witness{ + InvocationScript: make([]byte, 33), + VerificationScript: make([]byte, 40), + }, + } + random.Fill(ep.Data) + random.Fill(ep.Witness.InvocationScript) + random.Fill(ep.Witness.VerificationScript) + msg := NewMessage(CMDExtensible, ep) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := msg.Bytes() + if err != nil { + b.FailNow() + } + } +} + func TestEncodeDecodeHeaders(t *testing.T) { // shouldn't try to compress headers payload headers := &payload.Headers{Hdrs: make([]*block.Header, CompressionMinSize)} From 73e4040628bfab5136cb8aecc09bc9f5e4e4afef Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 6 Aug 2021 11:32:02 +0300 Subject: [PATCH 5/5] mpt: use `BinWriter.Grow()` instead of custom buffer Also add benchmarks. Signed-off-by: Evgeniy Stratonikov --- pkg/core/mpt/base.go | 9 ++++----- pkg/core/mpt/bench_test.go | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 pkg/core/mpt/bench_test.go diff --git a/pkg/core/mpt/base.go b/pkg/core/mpt/base.go index aa5336880..c2f7f2f11 100644 --- a/pkg/core/mpt/base.go +++ b/pkg/core/mpt/base.go @@ -1,7 +1,6 @@ package mpt import ( - "bytes" "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -64,10 +63,10 @@ func (b *BaseNode) updateHash(n Node) { // updateCache updates hash and bytes fields for this BaseNode. func (b *BaseNode) updateBytes(n Node) { - buf := bytes.NewBuffer(make([]byte, 0, 1+n.Size())) - bw := io.NewBinWriterFromIO(buf) - encodeNodeWithType(n, bw) - b.bytes = buf.Bytes() + bw := io.NewBufBinWriter() + bw.Grow(1 + n.Size()) + encodeNodeWithType(n, bw.BinWriter) + b.bytes = bw.Bytes() b.bytesValid = true } diff --git a/pkg/core/mpt/bench_test.go b/pkg/core/mpt/bench_test.go new file mode 100644 index 000000000..7c3c00d51 --- /dev/null +++ b/pkg/core/mpt/bench_test.go @@ -0,0 +1,39 @@ +package mpt + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/internal/random" +) + +func benchmarkBytes(b *testing.B, n Node) { + inv := n.(interface{ invalidateCache() }) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + inv.invalidateCache() + _ = n.Bytes() + } +} + +func BenchmarkBytes(b *testing.B) { + b.Run("extension", func(b *testing.B) { + n := NewExtensionNode(random.Bytes(10), NewLeafNode(random.Bytes(10))) + benchmarkBytes(b, n) + }) + b.Run("leaf", func(b *testing.B) { + n := NewLeafNode(make([]byte, 15)) + benchmarkBytes(b, n) + }) + b.Run("hash", func(b *testing.B) { + n := NewHashNode(random.Uint256()) + benchmarkBytes(b, n) + }) + b.Run("branch", func(b *testing.B) { + n := NewBranchNode() + n.Children[0] = NewLeafNode(random.Bytes(10)) + n.Children[4] = NewLeafNode(random.Bytes(10)) + n.Children[7] = NewLeafNode(random.Bytes(10)) + n.Children[8] = NewLeafNode(random.Bytes(10)) + }) +}