bigint: don't allocate in ToPreallocatedBytes

Turns out, it's almost always allocating because we're mostly dealing with
small integers while the buffer size is calculated in 8-byte chunks here, so
preallocated buffer is always insufficient.

name                   old time/op    new time/op    delta
ToPreallocatedBytes-8    28.5ns ± 7%    19.7ns ± 5%   -30.72%  (p=0.000 n=10+10)

name                   old alloc/op   new alloc/op   delta
ToPreallocatedBytes-8     16.0B ± 0%      0.0B       -100.00%  (p=0.000 n=10+10)

name                   old allocs/op  new allocs/op  delta
ToPreallocatedBytes-8      1.00 ± 0%      0.00       -100.00%  (p=0.000 n=10+10)

Fix StorageItem reuse at the same time. We don't copy when getting values from
the storage, but we don when we're putting them, so buffer reuse could corrupt
old values.
This commit is contained in:
Roman Khimov 2022-05-31 23:10:56 +03:00
parent c3d989ebda
commit 9a06995460
8 changed files with 38 additions and 42 deletions

View file

@ -7,12 +7,14 @@ import (
"errors" "errors"
"fmt" "fmt"
iocore "io" iocore "io"
"math/big"
"sync" "sync"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -387,6 +389,14 @@ func (dao *Simple) PutStorageItem(id int32, key []byte, si state.StorageItem) {
dao.Store.Put(stKey, si) dao.Store.Put(stKey, si)
} }
// PutBigInt serializaed and puts the given integer for the given id with the given
// key into the given store.
func (dao *Simple) PutBigInt(id int32, key []byte, n *big.Int) {
var buf [bigint.MaxBytesLen]byte
stData := bigint.ToPreallocatedBytes(n, buf[:])
dao.PutStorageItem(id, key, stData)
}
// DeleteStorageItem drops a storage item for the given id with the // DeleteStorageItem drops a storage item for the given id with the
// given key from the store. // given key from the store.
func (dao *Simple) DeleteStorageItem(id int32, key []byte) { func (dao *Simple) DeleteStorageItem(id int32, key []byte) {

View file

@ -601,8 +601,7 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
id := bigint.FromBytes(si) id := bigint.FromBytes(si)
ret := int32(id.Int64()) ret := int32(id.Int64())
id.Add(id, intOne) id.Add(id, intOne)
si = bigint.ToPreallocatedBytes(id, si) d.PutBigInt(m.ID, keyNextAvailableID, id)
d.PutStorageItem(m.ID, keyNextAvailableID, si)
return ret, nil return ret, nil
} }

View file

@ -440,7 +440,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
} }
cache.gasPerVoteCache[cs[i].Key] = *tmp cache.gasPerVoteCache[cs[i].Key] = *tmp
ic.DAO.PutStorageItem(n.ID, key, bigint.ToBytes(tmp)) ic.DAO.PutBigInt(n.ID, key, tmp)
} }
} }
} }
@ -1088,8 +1088,7 @@ func (n *NEO) modifyVoterTurnout(d *dao.Simple, amount *big.Int) error {
} }
votersCount := bigint.FromBytes(si) votersCount := bigint.FromBytes(si)
votersCount.Add(votersCount, amount) votersCount.Add(votersCount, amount)
si = bigint.ToPreallocatedBytes(votersCount, si) d.PutBigInt(n.ID, key, votersCount)
d.PutStorageItem(n.ID, key, si)
return nil return nil
} }
@ -1218,5 +1217,5 @@ func (n *NEO) putGASRecord(dao *dao.Simple, index uint32, value *big.Int) {
key := make([]byte, 5) key := make([]byte, 5)
key[0] = prefixGASPerBlock key[0] = prefixGASPerBlock
binary.BigEndian.PutUint32(key[1:], index) binary.BigEndian.PutUint32(key[1:], index)
dao.PutStorageItem(n.ID, key, bigint.ToBytes(value)) dao.PutBigInt(n.ID, key, value)
} }

View file

@ -108,8 +108,7 @@ func (c *nep17TokenNative) getTotalSupply(d *dao.Simple) (state.StorageItem, *bi
} }
func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) { func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) {
si = state.StorageItem(bigint.ToPreallocatedBytes(supply, si)) d.PutBigInt(c.ID, totalSupplyKey, supply)
d.PutStorageItem(c.ID, totalSupplyKey, si)
} }
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {

View file

@ -355,8 +355,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
itemID := bigint.FromBytes(si) itemID := bigint.FromBytes(si)
id := itemID.Uint64() id := itemID.Uint64()
itemID.Add(itemID, intOne) itemID.Add(itemID, intOne)
si = bigint.ToPreallocatedBytes(itemID, si) ic.DAO.PutBigInt(o.ID, prefixRequestID, itemID)
ic.DAO.PutStorageItem(o.ID, prefixRequestID, si)
// Should be executed from the contract. // Should be executed from the contract.
_, err := ic.GetContract(ic.VM.GetCallingScriptHash()) _, err := ic.GetContract(ic.VM.GetCallingScriptHash())

View file

@ -37,7 +37,7 @@ func putConvertibleToDAO(id int32, d *dao.Simple, key []byte, conv stackitem.Con
} }
func setIntWithKey(id int32, dao *dao.Simple, key []byte, value int64) { func setIntWithKey(id int32, dao *dao.Simple, key []byte, value int64) {
dao.PutStorageItem(id, key, bigint.ToBytes(big.NewInt(value))) dao.PutBigInt(id, key, big.NewInt(value))
} }
func getIntWithKey(id int32, dao *dao.Simple, key []byte) int64 { func getIntWithKey(id int32, dao *dao.Simple, key []byte) int64 {

View file

@ -0,0 +1,15 @@
package bigint
import (
"math/big"
"testing"
)
func BenchmarkToPreallocatedBytes(b *testing.B) {
v := big.NewInt(100500)
buf := make([]byte, 4)
for i := 0; i < b.N; i++ {
_ = ToPreallocatedBytes(v, buf[:0])
}
}

View file

@ -1,7 +1,6 @@
package bigint package bigint
import ( import (
"encoding/binary"
"math/big" "math/big"
"math/bits" "math/bits"
@ -109,36 +108,26 @@ func ToBytes(n *big.Int) []byte {
func ToPreallocatedBytes(n *big.Int, data []byte) []byte { func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
sign := n.Sign() sign := n.Sign()
if sign == 0 { if sign == 0 {
return data return data[:0]
} }
if sign < 0 { if sign < 0 {
n.Add(n, bigOne) n.Add(n, bigOne)
defer func() { n.Sub(n, bigOne) }() defer func() { n.Sub(n, bigOne) }()
if n.Sign() == 0 { // n == -1 if n.Sign() == 0 { // n == -1
return append(data, 0xFF) return append(data[:0], 0xFF)
} }
} }
var ws = n.Bits() lb := n.BitLen()/8 + 1
lb := len(ws) * wordSizeBytes
if c := cap(data); c < lb { if c := cap(data); c < lb {
data = make([]byte, lb, lb+1) data = make([]byte, lb)
} else { } else {
data = data[:lb] data = data[:lb]
} }
data = wordsToBytes(ws, data) _ = n.FillBytes(data)
slice.Reverse(data)
size := len(data)
for ; data[size-1] == 0; size-- {
}
data = data[:size]
if data[size-1]&0x80 != 0 {
data = append(data, 0)
}
if sign == -1 { if sign == -1 {
for i := range data { for i := range data {
@ -148,17 +137,3 @@ func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
return data return data
} }
func wordsToBytes(ws []big.Word, bs []byte) []byte {
if wordSizeBytes == 8 {
for i := range ws {
binary.LittleEndian.PutUint64(bs[i*wordSizeBytes:], uint64(ws[i]))
}
} else {
for i := range ws {
binary.LittleEndian.PutUint32(bs[i*wordSizeBytes:], uint32(ws[i]))
}
}
return bs
}