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:
parent
c3d989ebda
commit
9a06995460
8 changed files with 38 additions and 42 deletions
|
@ -7,12 +7,14 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
iocore "io"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"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/storage"
|
||||
"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/smartcontract/trigger"
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// given key from the store.
|
||||
func (dao *Simple) DeleteStorageItem(id int32, key []byte) {
|
||||
|
|
|
@ -601,8 +601,7 @@ func (m *Management) getNextContractID(d *dao.Simple) (int32, error) {
|
|||
id := bigint.FromBytes(si)
|
||||
ret := int32(id.Int64())
|
||||
id.Add(id, intOne)
|
||||
si = bigint.ToPreallocatedBytes(id, si)
|
||||
d.PutStorageItem(m.ID, keyNextAvailableID, si)
|
||||
d.PutBigInt(m.ID, keyNextAvailableID, id)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -440,7 +440,7 @@ func (n *NEO) PostPersist(ic *interop.Context) error {
|
|||
}
|
||||
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.Add(votersCount, amount)
|
||||
si = bigint.ToPreallocatedBytes(votersCount, si)
|
||||
d.PutStorageItem(n.ID, key, si)
|
||||
d.PutBigInt(n.ID, key, votersCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1218,5 +1217,5 @@ func (n *NEO) putGASRecord(dao *dao.Simple, index uint32, value *big.Int) {
|
|||
key := make([]byte, 5)
|
||||
key[0] = prefixGASPerBlock
|
||||
binary.BigEndian.PutUint32(key[1:], index)
|
||||
dao.PutStorageItem(n.ID, key, bigint.ToBytes(value))
|
||||
dao.PutBigInt(n.ID, key, value)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
si = state.StorageItem(bigint.ToPreallocatedBytes(supply, si))
|
||||
d.PutStorageItem(c.ID, totalSupplyKey, si)
|
||||
d.PutBigInt(c.ID, totalSupplyKey, supply)
|
||||
}
|
||||
|
||||
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
|
|
|
@ -355,8 +355,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
|
|||
itemID := bigint.FromBytes(si)
|
||||
id := itemID.Uint64()
|
||||
itemID.Add(itemID, intOne)
|
||||
si = bigint.ToPreallocatedBytes(itemID, si)
|
||||
ic.DAO.PutStorageItem(o.ID, prefixRequestID, si)
|
||||
ic.DAO.PutBigInt(o.ID, prefixRequestID, itemID)
|
||||
|
||||
// Should be executed from the contract.
|
||||
_, err := ic.GetContract(ic.VM.GetCallingScriptHash())
|
||||
|
|
|
@ -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) {
|
||||
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 {
|
||||
|
|
15
pkg/encoding/bigint/bench_test.go
Normal file
15
pkg/encoding/bigint/bench_test.go
Normal 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])
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package bigint
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
"math/bits"
|
||||
|
||||
|
@ -109,36 +108,26 @@ func ToBytes(n *big.Int) []byte {
|
|||
func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
||||
sign := n.Sign()
|
||||
if sign == 0 {
|
||||
return data
|
||||
return data[:0]
|
||||
}
|
||||
|
||||
if sign < 0 {
|
||||
n.Add(n, bigOne)
|
||||
defer func() { n.Sub(n, bigOne) }()
|
||||
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 {
|
||||
data = make([]byte, lb, lb+1)
|
||||
data = make([]byte, lb)
|
||||
} else {
|
||||
data = data[:lb]
|
||||
}
|
||||
data = wordsToBytes(ws, data)
|
||||
|
||||
size := len(data)
|
||||
for ; data[size-1] == 0; size-- {
|
||||
}
|
||||
|
||||
data = data[:size]
|
||||
|
||||
if data[size-1]&0x80 != 0 {
|
||||
data = append(data, 0)
|
||||
}
|
||||
_ = n.FillBytes(data)
|
||||
slice.Reverse(data)
|
||||
|
||||
if sign == -1 {
|
||||
for i := range data {
|
||||
|
@ -148,17 +137,3 @@ func ToPreallocatedBytes(n *big.Int, data []byte) []byte {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue