Skip to content

Commit

Permalink
Inline child array/map data slab into parent slab
Browse files Browse the repository at this point in the history
Currently, every array or map are stored in its own slab and
parent slab refers to child array or map by SlabID.

The current approach can lead to many small slabs, especially for
Cadence data structures with multiple nested levels.

This commit inlines child array/map in parent slab when:
- child array/map fits in one slab (root slab is data slab)
- encoded size of inlined child array/map is less than the
  max inline size limit enforced by parent

This commit optimizes encoding size by:
- reusing inlined array types
- reusing seed, digests, and field names of inlined composite types

Also update debugging code to handle inlined array/map element.
  • Loading branch information
fxamacker committed Sep 14, 2023
1 parent 1e6ec55 commit d6f3daa
Show file tree
Hide file tree
Showing 18 changed files with 14,336 additions and 4,396 deletions.
657 changes: 583 additions & 74 deletions array.go

Large diffs are not rendered by default.

236 changes: 185 additions & 51 deletions array_debug.go

Large diffs are not rendered by default.

4,959 changes: 3,724 additions & 1,235 deletions array_test.go

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions basicarray.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func newBasicArrayDataSlabFromData(
)
}

cborDec := decMode.NewByteStreamDecoder(data[2:])
cborDec := decMode.NewByteStreamDecoder(data[versionAndFlagSize:])

elemCount, err := cborDec.DecodeArrayHead()
if err != nil {
Expand All @@ -85,7 +85,7 @@ func newBasicArrayDataSlabFromData(

elements := make([]Storable, elemCount)
for i := 0; i < int(elemCount); i++ {
storable, err := decodeStorable(cborDec, SlabIDUndefined)
storable, err := decodeStorable(cborDec, id, nil)
if err != nil {
// Wrap err as external error (if needed) because err is returned by StorableDecoder callback.
return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to decode array element")
Expand All @@ -101,10 +101,17 @@ func newBasicArrayDataSlabFromData(

func (a *BasicArrayDataSlab) Encode(enc *Encoder) error {

flag := maskBasicArray | maskSlabRoot
const version = 1

h, err := newArraySlabHead(version, slabBasicArray)
if err != nil {
return NewEncodingError(err)
}

h.setRoot()

// Encode flag
_, err := enc.Write([]byte{0x0, flag})
_, err = enc.Write(h[:])
if err != nil {
return NewEncodingError(err)
}
Expand Down
10 changes: 9 additions & 1 deletion cmd/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ type testTypeInfo struct{}

var _ atree.TypeInfo = testTypeInfo{}

func (testTypeInfo) IsComposite() bool {
return false
}

func (i testTypeInfo) ID() string {
return fmt.Sprintf("uint64(%d)", i)
}

func (testTypeInfo) Encode(e *cbor.StreamEncoder) error {
return e.EncodeUint8(42)
}
Expand All @@ -86,7 +94,7 @@ func (i testTypeInfo) Equal(other atree.TypeInfo) bool {
return ok
}

func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID) (atree.Storable, error) {
func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID, _ []atree.ExtraData) (atree.Storable, error) {
tagNumber, err := dec.DecodeTagNumber()
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion cmd/stress/storable.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ func (v StringValue) String() string {
return v.str
}

func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID) (atree.Storable, error) {
func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID, _ []atree.ExtraData) (atree.Storable, error) {
t, err := dec.NextType()
if err != nil {
return nil, err
Expand Down
10 changes: 10 additions & 0 deletions cmd/stress/typeinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package main

import (
"fmt"

"github.com/onflow/atree"

"github.com/fxamacker/cbor/v2"
Expand All @@ -30,6 +32,14 @@ type testTypeInfo struct {

var _ atree.TypeInfo = testTypeInfo{}

func (i testTypeInfo) IsComposite() bool {
return false
}

func (i testTypeInfo) ID() string {
return fmt.Sprintf("uint64(%d)", i)
}

func (i testTypeInfo) Encode(e *cbor.StreamEncoder) error {
return e.EncodeUint64(i.value)
}
Expand Down
33 changes: 29 additions & 4 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,45 @@ type Encoder struct {
io.Writer
CBOR *cbor.StreamEncoder
Scratch [64]byte
encMode cbor.EncMode
}

func NewEncoder(w io.Writer, encMode cbor.EncMode) *Encoder {
streamEncoder := encMode.NewStreamEncoder(w)
return &Encoder{
Writer: w,
CBOR: streamEncoder,
Writer: w,
CBOR: streamEncoder,
encMode: encMode,
}
}

// encodeStorableAsElement encodes storable as Array or OrderedMap element.
// Storable is encode as an inlined ArrayDataSlab or MapDataSlab if it is ArrayDataSlab or MapDataSlab.
func encodeStorableAsElement(enc *Encoder, storable Storable, inlinedTypeInfo *inlinedExtraData) error {

switch storable := storable.(type) {

case *ArrayDataSlab:
return storable.encodeAsInlined(enc, inlinedTypeInfo)

case *MapDataSlab:
return storable.encodeAsInlined(enc, inlinedTypeInfo)

default:
err := storable.Encode(enc)
if err != nil {
// Wrap err as external error (if needed) because err is returned by Storable interface.
return wrapErrorfAsExternalErrorIfNeeded(err, "failed to encode map value")
}
}

return nil
}

type StorableDecoder func(
decoder *cbor.StreamDecoder,
storableSlabID SlabID,
inlinedExtraData []ExtraData,
) (
Storable,
error,
Expand Down Expand Up @@ -101,7 +127,7 @@ func DecodeSlab(

case slabStorable:
cborDec := decMode.NewByteStreamDecoder(data[versionAndFlagSize:])
storable, err := decodeStorable(cborDec, id)
storable, err := decodeStorable(cborDec, id, nil)
if err != nil {
// Wrap err as external error (if needed) because err is returned by StorableDecoder callback.
return nil, wrapErrorfAsExternalErrorIfNeeded(err, "failed to decode slab storable")
Expand All @@ -116,7 +142,6 @@ func DecodeSlab(
}
}

// TODO: make it inline
func GetUintCBORSize(n uint64) uint32 {
if n <= 23 {
return 1
Expand Down
Loading

0 comments on commit d6f3daa

Please sign in to comment.