stackitem: cache serialized results in EncodeBinary

```
name            old time/op    new time/op    delta
EncodeBinary-8    8.39ms ± 1%    0.05ms ±21%  -99.44%  (p=0.000 n=10+9)

name            old alloc/op   new alloc/op   delta
EncodeBinary-8    2.24MB ± 0%    0.33MB ± 0%  -85.29%  (p=0.000 n=9+10)

name            old allocs/op  new allocs/op  delta
EncodeBinary-8     65.6k ± 0%      0.0k ± 0%  -99.95%  (p=0.000 n=10+10)
```

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2021-07-09 16:09:33 +03:00
parent 17a3f17c74
commit 8fdc7e32f5

View file

@ -22,14 +22,14 @@ type serContext struct {
uv [9]byte uv [9]byte
data []byte data []byte
allowInvalid bool allowInvalid bool
seen map[Item]bool seen map[Item]sliceNoPointer
} }
// Serialize encodes given Item into the byte slice. // Serialize encodes given Item into the byte slice.
func Serialize(item Item) ([]byte, error) { func Serialize(item Item) ([]byte, error) {
sc := serContext{ sc := serContext{
allowInvalid: false, allowInvalid: false,
seen: make(map[Item]bool), seen: make(map[Item]sliceNoPointer),
} }
err := sc.serialize(item) err := sc.serialize(item)
if err != nil { if err != nil {
@ -58,7 +58,7 @@ func EncodeBinary(item Item, w *io.BinWriter) {
func EncodeBinaryProtected(item Item, w *io.BinWriter) { func EncodeBinaryProtected(item Item, w *io.BinWriter) {
sc := serContext{ sc := serContext{
allowInvalid: true, allowInvalid: true,
seen: make(map[Item]bool), seen: make(map[Item]sliceNoPointer),
} }
err := sc.serialize(item) err := sc.serialize(item)
if err != nil { if err != nil {
@ -69,10 +69,18 @@ func EncodeBinaryProtected(item Item, w *io.BinWriter) {
} }
func (w *serContext) serialize(item Item) error { func (w *serContext) serialize(item Item) error {
if w.seen[item] { if v, ok := w.seen[item]; ok {
if v.start == v.end {
return ErrRecursive return ErrRecursive
} }
if len(w.data)+v.end-v.start > MaxSize {
return ErrTooBig
}
w.data = append(w.data, w.data[v.start:v.end]...)
return nil
}
start := len(w.data)
switch t := item.(type) { switch t := item.(type) {
case *ByteArray: case *ByteArray:
w.data = append(w.data, byte(ByteArrayT)) w.data = append(w.data, byte(ByteArrayT))
@ -103,7 +111,7 @@ func (w *serContext) serialize(item Item) error {
return fmt.Errorf("%w: Interop", ErrUnserializable) return fmt.Errorf("%w: Interop", ErrUnserializable)
} }
case *Array, *Struct: case *Array, *Struct:
w.seen[item] = true w.seen[item] = sliceNoPointer{}
_, isArray := t.(*Array) _, isArray := t.(*Array)
if isArray { if isArray {
@ -119,9 +127,9 @@ func (w *serContext) serialize(item Item) error {
return err return err
} }
} }
delete(w.seen, item) w.seen[item] = sliceNoPointer{start, len(w.data)}
case *Map: case *Map:
w.seen[item] = true w.seen[item] = sliceNoPointer{}
elems := t.Value().([]MapElement) elems := t.Value().([]MapElement)
w.data = append(w.data, byte(MapT)) w.data = append(w.data, byte(MapT))
@ -134,7 +142,7 @@ func (w *serContext) serialize(item Item) error {
return err return err
} }
} }
delete(w.seen, item) w.seen[item] = sliceNoPointer{start, len(w.data)}
case Null: case Null:
w.data = append(w.data, byte(AnyT)) w.data = append(w.data, byte(AnyT))
case nil: case nil: