Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize storage format #797

Merged
merged 16 commits into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
no need to sort, fields are already ordered
  • Loading branch information
turbolent committed Apr 6, 2021
commit d302379c6c3de11f9256473282b27a5578f2a60d
27 changes: 7 additions & 20 deletions runtime/interpreter/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"io"
"math/big"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -808,25 +807,12 @@ func (e *Encoder) prepareCompositeValue(
interface{},
error,
) {
fieldPairs := v.Fields.pairs
fieldLen := len(fieldPairs)
fields := make(cborArray, v.Fields.Len()*2)

// Sort field names lexicographically.
fieldNames := make([]string, fieldLen)

index := 0
for name := range fieldPairs {
fieldNames[index] = name
index++
}

sort.Strings(fieldNames)

// Create fields (key and value) array, sorted by field name.
fields := make(cborArray, fieldLen*2)

for i, fieldName := range fieldNames {
value := fieldPairs[fieldName].Value
i := 0
for pair := v.Fields.Oldest(); pair != nil; pair = pair.Next() {
fieldName := pair.Key
value := pair.Value

valuePath := append(path[:], fieldName)

Expand All @@ -835,7 +821,8 @@ func (e *Encoder) prepareCompositeValue(
return nil, err
}

fields[i*2], fields[i*2+1] = fieldName, prepared
fields[i], fields[i+1] = fieldName, prepared
i += 2
}

location, err := e.prepareLocation(v.Location)
Expand Down
152 changes: 104 additions & 48 deletions runtime/tests/interpreter/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7918,11 +7918,11 @@ func TestInterpretForce(t *testing.T) {
})
}

func permutations(xs []string) (res [][]string) {
var f func([]string, int)
f = func(a []string, k int) {
func permutations(xs [][]byte) (res [][][]byte) {
var f func([][]byte, int)
f = func(a [][]byte, k int) {
if k == len(a) {
res = append(res, append([]string{}, a...))
res = append(res, append([][]byte{}, a...))
} else {
for i := k; i < len(xs); i++ {
a[k], a[i] = a[i], a[k]
Expand All @@ -7941,43 +7941,92 @@ func TestInterpretCompositeValueFieldEncodingOrder(t *testing.T) {

t.Parallel()

fieldValues := map[string]int{
"a": 1,
"b": 2,
"c": 3,
fieldValues := map[byte]byte{
'a': 1,
'b': 2,
'c': 3,
}

initializations := make([]string, 0, len(fieldValues))
// prepare initialization statements

initializations := make([][]byte, 0, len(fieldValues))
expectedEncodings := make([][]byte, 0, len(fieldValues))

for name, value := range fieldValues {
initialization := fmt.Sprintf("self.%s = %d", name, value)
initializations = append(initializations, initialization)
initialization := fmt.Sprintf("self.%c = %d", name, value)
initializations = append(initializations, []byte(initialization))

expectedEncodings = append(expectedEncodings, []byte{
// UTF-8 string, length 1
0x61,
name,
// tag
0xD8, 0x98,
// - positive bignum
0xc2,
// - byte string, length 1
0x41,
value,
})
}

allInitializations := permutations(initializations)
allExpectedEncodings := permutations(expectedEncodings)

encodings := make([][]byte, len(allInitializations))
expectedPrefix := []byte{
// tag
0xd8, 0x84,
// array, 5 items follow
0x85,

// tag
0xd8, 0xc1,
// UTF-8 string, length 4
0x64,
// t, e, s, t
0x74, 0x65, 0x73, 0x74,

// nil
0xf6,

// positive integer 1
0x1,

// array, 6 items follow
0x86,
}

expectedSuffix := []byte{
// UTF-8 string, length 4
0x64,
0x54, 0x65, 0x73, 0x74,
}

for i, initialization := range allInitializations {

inter := parseCheckAndInterpret(t,
fmt.Sprintf(
`
struct Test {
let a: Int
let b: Int
let c: Int
var codeBuilder strings.Builder
codeBuilder.WriteString(`
struct Test {
let a: Int
let b: Int
let c: Int

init() {
%s
}
}
init() {
`)

let test = Test()
`,
strings.Join(initialization, "\n"),
),
)
for _, statement := range initialization {
codeBuilder.Write(statement)
codeBuilder.WriteRune('\n')
}

codeBuilder.WriteString(`
}
}

let test = Test()
`)

inter := parseCheckAndInterpret(t, codeBuilder.String())

test := inter.Globals["test"].GetValue().(*interpreter.CompositeValue)

Expand All @@ -7988,13 +8037,15 @@ func TestInterpretCompositeValueFieldEncodingOrder(t *testing.T) {
encoded, _, err := interpreter.EncodeValue(test, nil, false, nil)
require.NoError(t, err)

encodings[i] = encoded
}
expected := expectedPrefix[:]

for _, expectedEncoding := range allExpectedEncodings[i] {
expected = append(expected, expectedEncoding...)
}

expected := encodings[0]
expected = append(expected, expectedSuffix...)

for _, actual := range encodings[1:] {
require.Equal(t, expected, actual)
assert.Equal(t, expected, encoded)
}
}

Expand All @@ -8008,29 +8059,34 @@ func TestInterpretDictionaryValueEncodingOrder(t *testing.T) {
"c": 3,
}

initializations := make([]string, 0, len(fieldValues))
initializations := make([][]byte, 0, len(fieldValues))

for name, value := range fieldValues {
initialization := fmt.Sprintf(`xs["%s"] = %d`, name, value)
initializations = append(initializations, initialization)
initializations = append(initializations, []byte(initialization))
}

for _, initialization := range permutations(initializations) {

inter := parseCheckAndInterpret(t,
fmt.Sprintf(
`
fun construct(): {String: Int} {
let xs: {String: Int} = {}
%s
return xs
}
var codeBuilder strings.Builder
codeBuilder.WriteString(`
fun construct(): {String: Int} {
let xs: {String: Int} = {}
`)

let test = construct()
`,
strings.Join(initialization, "\n"),
),
)
for _, statement := range initialization {
codeBuilder.Write(statement)
codeBuilder.WriteRune('\n')
}

codeBuilder.WriteString(`
return xs
}

let test = construct()
`)

inter := parseCheckAndInterpret(t, codeBuilder.String())

test := inter.Globals["test"].GetValue().(*interpreter.DictionaryValue)

Expand Down