Reuse AST storage between top-level expressions

```
Comparing:
	old: v2-wip/1da2fc7 (2021-03-25 20:38:05 -0400 -0400)
	run: v2-wip/3f23ab9 (2021-03-25 22:35:06 -0400 -0400)
-----------------------------------------------------------
name                  old time/op    new time/op    delta
UnmarshalSimple/v2-8     700ns ± 3%     705ns ± 2%     ~     (p=0.690 n=5+5)
UnmarshalSimple/v1-8    3.85µs ± 1%    4.02µs ± 4%   +4.19%  (p=0.032 n=5+5)
UnmarshalSimple/bs-8    2.34µs ± 2%    2.38µs ± 3%     ~     (p=0.310 n=5+5)
ReferenceFile/v2-8      32.2µs ±13%    23.9µs ± 1%  -25.79%  (p=0.008 n=5+5)
ReferenceFile/v1-8       270µs ± 2%     264µs ± 2%     ~     (p=0.095 n=5+5)
ReferenceFile/bs-8       291µs ± 0%     294µs ± 0%   +0.88%  (p=0.008 n=5+5)

name                  old alloc/op   new alloc/op   delta
ReferenceFile/v2-8      37.1kB ± 0%     6.7kB ± 0%  -81.91%  (p=0.008 n=5+5)
ReferenceFile/v1-8       131kB ± 0%     131kB ± 0%     ~     (p=0.444 n=5+5)
ReferenceFile/bs-8      80.8kB ± 0%    80.8kB ± 0%     ~     (p=0.571 n=5+5)

name                  old allocs/op  new allocs/op  delta
ReferenceFile/v2-8         152 ± 0%       148 ± 0%   -2.63%  (p=0.008 n=5+5)
ReferenceFile/v1-8       2.65k ± 0%     2.65k ± 0%     ~     (all equal)
ReferenceFile/bs-8       1.73k ± 0%     1.73k ± 0%     ~     (all equal)

~/s/g/p/g/benchmark$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/pelletier/go-toml/v2/benchmark
cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz
BenchmarkUnmarshalSimple/v2-8         	 1692444	       710.7 ns/op
BenchmarkUnmarshalSimple/v1-8         	  307609	      3862 ns/op
BenchmarkUnmarshalSimple/bs-8         	  520429	      2285 ns/op
BenchmarkReferenceFile/v2-8           	   50395	     24006 ns/op	    6704 B/op	     148 allocs/op
BenchmarkReferenceFile/v1-8           	    4144	    264655 ns/op	  130567 B/op	    2649 allocs/op
BenchmarkReferenceFile/bs-8           	    3969	    293635 ns/op	   80784 B/op	    1729 allocs/op
PASS
ok  	github.com/pelletier/go-toml/v2/benchmark	8.143s
```
This commit is contained in:
Thomas Pelletier
2021-03-25 22:37:16 -04:00
parent 3f23ab97e0
commit 390927a0cd
5 changed files with 188 additions and 473 deletions
+5 -315
View File
@@ -1,13 +1,12 @@
package toml
package toml_test
import (
"math"
"testing"
"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pelletier/go-toml/v2/internal/ast"
)
func TestUnmarshal_Integers(t *testing.T) {
@@ -61,7 +60,7 @@ func TestUnmarshal_Integers(t *testing.T) {
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
doc := doc{}
err := Unmarshal([]byte(`A = `+e.input), &doc)
err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
require.NoError(t, err)
assert.Equal(t, e.expected, doc.A)
})
@@ -157,7 +156,7 @@ func TestUnmarshal_Floats(t *testing.T) {
for _, e := range examples {
t.Run(e.desc, func(t *testing.T) {
doc := doc{}
err := Unmarshal([]byte(`A = `+e.input), &doc)
err := toml.Unmarshal([]byte(`A = `+e.input), &doc)
require.NoError(t, err)
if e.testFn != nil {
e.testFn(t, doc.A)
@@ -648,7 +647,7 @@ B = "data"`,
if test.err && test.expected != nil {
panic("invalid test: cannot expect both an error and a value")
}
err := Unmarshal([]byte(e.input), test.target)
err := toml.Unmarshal([]byte(e.input), test.target)
if test.err {
require.Error(t, err)
} else {
@@ -658,312 +657,3 @@ B = "data"`,
})
}
}
func TestFromAst_KV(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`hello`),
},
{
Kind: ast.Key,
Data: []byte(`Foo`),
},
},
},
}
type Doc struct {
Foo string
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{Foo: "hello"}, x)
}
func TestFromAst_Table(t *testing.T) {
t.Run("one level table on struct", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.Table,
Children: []astNode{
{Kind: ast.Key, Data: []byte(`Level1`)},
},
},
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`hello`),
},
{
Kind: ast.Key,
Data: []byte(`A`),
},
},
},
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`world`),
},
{
Kind: ast.Key,
Data: []byte(`B`),
},
},
},
}
type Level1 struct {
A string
B string
}
type Doc struct {
Level1 Level1
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{
Level1: Level1{
A: "hello",
B: "world",
},
}, x)
})
t.Run("one level table on struct", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.Table,
Children: []astNode{
{Kind: ast.Key, Data: []byte(`A`)},
{Kind: ast.Key, Data: []byte(`B`)},
},
},
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`value`),
},
{
Kind: ast.Key,
Data: []byte(`C`),
},
},
},
}
type B struct {
C string
}
type A struct {
B B
}
type Doc struct {
A A
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{
A: A{B: B{C: "value"}},
}, x)
})
}
func TestFromAst_InlineTable(t *testing.T) {
t.Run("one level of strings", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.InlineTable,
Children: []astNode{
{
Kind: ast.KeyValue,
Children: []astNode{
{Kind: ast.String, Data: []byte(`Tom`)},
{Kind: ast.Key, Data: []byte(`First`)},
},
},
{
Kind: ast.KeyValue,
Children: []astNode{
{Kind: ast.String, Data: []byte(`Preston-Werner`)},
{Kind: ast.Key, Data: []byte(`Last`)},
},
},
},
},
{
Kind: ast.Key,
Data: []byte(`Name`),
},
},
},
}
type Name struct {
First string
Last string
}
type Doc struct {
Name Name
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{
Name: Name{
First: "Tom",
Last: "Preston-Werner",
},
}, x)
})
}
func TestFromAst_Slice(t *testing.T) {
t.Run("slice of string", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.Array,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`hello`),
},
{
Kind: ast.String,
Data: []byte(`world`),
},
},
},
{
Kind: ast.Key,
Data: []byte(`Foo`),
},
},
},
}
type Doc struct {
Foo []string
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x)
})
t.Run("slice of interfaces for strings", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.Array,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`hello`),
},
{
Kind: ast.String,
Data: []byte(`world`),
},
},
},
{
Kind: ast.Key,
Data: []byte(`Foo`),
},
},
},
}
type Doc struct {
Foo []interface{}
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x)
})
t.Run("slice of interfaces with slices", func(t *testing.T) {
root := astRoot{
astNode{
Kind: ast.KeyValue,
Children: []astNode{
{
Kind: ast.Array,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`hello`),
},
{
Kind: ast.Array,
Children: []astNode{
{
Kind: ast.String,
Data: []byte(`inner1`),
},
{
Kind: ast.String,
Data: []byte(`inner2`),
},
},
},
},
},
{
Kind: ast.Key,
Data: []byte(`Foo`),
},
},
},
}
type Doc struct {
Foo []interface{}
}
x := Doc{}
d := decoder{}
err := d.fromAst(root.toOrig(), &x)
require.NoError(t, err)
assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x)
})
}