*: do not use WriteArray for frequently used items

`WriteArray` involves reflection, it makes sense to optimize
serialization of transactions and application logs which are serialized
constantly. Adding case in a type switch in `WriteArray` is not an
option because we don't want new dependencies for `io` package.

```
name                          old time/op    new time/op    delta
AppExecResult_EncodeBinary-8     852ns ± 3%     656ns ± 2%  -22.94%  (p=0.000 n=10+9)

name                          old alloc/op   new alloc/op   delta
AppExecResult_EncodeBinary-8      448B ± 0%      376B ± 0%  -16.07%  (p=0.000 n=10+10)

name                          old allocs/op  new allocs/op  delta
AppExecResult_EncodeBinary-8      7.00 ± 0%      5.00 ± 0%  -28.57%  (p=0.000 n=10+10)
```

```
name                 old time/op    new time/op    delta
Transaction_Bytes-8    1.29µs ± 3%    0.76µs ± 5%  -41.52%  (p=0.000 n=9+10)

name                 old alloc/op   new alloc/op   delta
Transaction_Bytes-8    1.21kB ± 0%    1.01kB ± 0%  -16.56%  (p=0.000 n=10+10)

name                 old allocs/op  new allocs/op  delta
Transaction_Bytes-8      12.0 ± 0%       7.0 ± 0%  -41.67%  (p=0.000 n=10+10)
```

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2021-08-06 11:55:43 +03:00
parent 43ee671f36
commit 291a29af1e
4 changed files with 52 additions and 4 deletions

View file

@ -61,7 +61,10 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
for _, it := range aer.Stack {
stackitem.EncodeBinaryProtected(it, w)
}
w.WriteArray(aer.Events)
w.WriteVarUint(uint64(len(aer.Events)))
for i := range aer.Events {
aer.Events[i].EncodeBinary(w)
}
w.WriteVarBytes([]byte(aer.FaultException))
}

View file

@ -13,6 +13,31 @@ import (
"github.com/stretchr/testify/require"
)
func BenchmarkAppExecResult_EncodeBinary(b *testing.B) {
aer := &AppExecResult{
Container: random.Uint256(),
Execution: Execution{
Trigger: trigger.Application,
VMState: vm.HaltState,
GasConsumed: 12345,
Stack: []stackitem.Item{},
Events: []NotificationEvent{{
ScriptHash: random.Uint160(),
Name: "Event",
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
}},
},
}
w := io.NewBufBinWriter()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
w.Reset()
aer.EncodeBinary(w.BinWriter)
}
}
func TestEncodeDecodeNotificationEvent(t *testing.T) {
event := &NotificationEvent{
ScriptHash: random.Uint160(),

View file

@ -53,3 +53,14 @@ func BenchmarkDecodeFromBytes(t *testing.B) {
require.NoError(t, err)
}
}
func BenchmarkTransaction_Bytes(b *testing.B) {
tx, err := NewTransactionFromBytes(benchTx)
require.NoError(b, err)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = tx.Bytes()
}
}

View file

@ -203,7 +203,10 @@ func (t *Transaction) DecodeBinary(br *io.BinReader) {
// EncodeBinary implements Serializable interface.
func (t *Transaction) EncodeBinary(bw *io.BinWriter) {
t.encodeHashableFields(bw)
bw.WriteArray(t.Scripts)
bw.WriteVarUint(uint64(len(t.Scripts)))
for i := range t.Scripts {
t.Scripts[i].EncodeBinary(bw)
}
}
// encodeHashableFields encodes the fields that are not used for
@ -218,8 +221,14 @@ func (t *Transaction) encodeHashableFields(bw *io.BinWriter) {
bw.WriteU64LE(uint64(t.SystemFee))
bw.WriteU64LE(uint64(t.NetworkFee))
bw.WriteU32LE(t.ValidUntilBlock)
bw.WriteArray(t.Signers)
bw.WriteArray(t.Attributes)
bw.WriteVarUint(uint64(len(t.Signers)))
for i := range t.Signers {
t.Signers[i].EncodeBinary(bw)
}
bw.WriteVarUint(uint64(len(t.Attributes)))
for i := range t.Attributes {
t.Attributes[i].EncodeBinary(bw)
}
bw.WriteVarBytes(t.Script)
}