Benchmark marshal (#550)

This commit is contained in:
Thomas Pelletier
2021-06-02 09:29:19 -04:00
committed by GitHub
parent b0d6c62255
commit f3bb20ea79
6 changed files with 246 additions and 78 deletions
+18 -13
View File
@@ -156,14 +156,17 @@ Execution time speedup compared to other Go TOML libraries:
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead>
<tbody>
<tr><td>HugoFrontMatter</td><td>2.6x</td><td>2.2x</td></tr>
<tr><td>ReferenceFile/map</td><td>2.8x</td><td>3.0x</td></tr>
<tr><td>ReferenceFile/struct</td><td>5.4x</td><td>6.2x</td></tr>
<tr><td>Marshal/HugoFrontMatter</td><td>1.9x</td><td>1.9x</td></tr>
<tr><td>Marshal/ReferenceFile/map</td><td>1.7x</td><td>1.9x</td></tr>
<tr><td>Marshal/ReferenceFile/struct</td><td>2.7x</td><td>2.9x</td></tr>
<tr><td>Unmarshal/HugoFrontMatter</td><td>2.9x</td><td>2.4x</td></tr>
<tr><td>Unmarshal/ReferenceFile/map</td><td>3.1x</td><td>3.0x</td></tr>
<tr><td>Unmarshal/ReferenceFile/struct</td><td>5.5x</td><td>5.8x</td></tr>
</tbody>
</table>
<details><summary>See more</summary>
<p>The table above has the results of the most common use-cases. The table
below contains the results of all benchmarks, including unrealistic ones. is
<p>The table above has the results of the most common use-cases. The table below
contains the results of all benchmarks, including unrealistic ones. It is
provided for completeness.</p>
<table>
@@ -171,14 +174,16 @@ provided for completeness.</p>
<tr><th>Benchmark</th><th>go-toml v1</th><th>BurntSushi/toml</th></tr>
</thead>
<tbody>
<tr><td>UnmarshalSimple/map</td><td>3.8x</td><td>2.4x</td></tr>
<tr><td>UnmarshalSimple/struct</td><td>5.4x</td><td>3.1x</td></tr>
<tr><td>UnmarshalDataset/example</td><td>2.8x</td><td>2.0x</td></tr>
<tr><td>UnmarshalDataset/code</td><td>1.8x</td><td>2.2x</td></tr>
<tr><td>UnmarshalDataset/twitter</td><td>2.5x</td><td>1.8x</td></tr>
<tr><td>UnmarshalDataset/citm_catalog</td><td>1.9x</td><td>1.2x</td></tr>
<tr><td>UnmarshalDataset/config</td><td>3.0x</td><td>2.5x</td></tr>
<tr><td>[Geo mean]</td><td>3.0x</td><td>2.4x</td></tr>
<tr><td>Marshal/SimpleDocument/map</td><td>1.8x</td><td>2.4x</td></tr>
<tr><td>Marshal/SimpleDocument/struct</td><td>2.7x</td><td>3.5x</td></tr>
<tr><td>Unmarshal/SimpleDocument/map</td><td>4.3x</td><td>2.4x</td></tr>
<tr><td>Unmarshal/SimpleDocument/struct</td><td>5.8x</td><td>3.3x</td></tr>
<tr><td>UnmarshalDataset/example</td><td>3.1x</td><td>2.2x</td></tr>
<tr><td>UnmarshalDataset/code</td><td>1.8x</td><td>2.1x</td></tr>
<tr><td>UnmarshalDataset/twitter</td><td>2.7x</td><td>1.9x</td></tr>
<tr><td>UnmarshalDataset/citm_catalog</td><td>1.8x</td><td>1.2x</td></tr>
<tr><td>UnmarshalDataset/config</td><td>3.4x</td><td>2.8x</td></tr>
<tr><td>[Geo mean]</td><td>2.8x</td><td>2.5x</td></tr>
</tbody>
</table>
<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>
+194 -59
View File
@@ -1,6 +1,7 @@
package benchmark_test
import (
"bytes"
"io/ioutil"
"testing"
"time"
@@ -21,15 +22,101 @@ func TestUnmarshalSimple(t *testing.T) {
}
}
func BenchmarkUnmarshalSimple(b *testing.B) {
doc := []byte(`A = "hello"`)
func BenchmarkUnmarshal(b *testing.B) {
b.Run("SimpleDocument", func(b *testing.B) {
doc := []byte(`A = "hello"`)
b.Run("struct", func(b *testing.B) {
b.SetBytes(int64(len(doc)))
b.Run("struct", func(b *testing.B) {
b.SetBytes(int64(len(doc)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := struct {
A string
}{}
err := toml.Unmarshal(doc, &d)
if err != nil {
panic(err)
}
}
})
b.Run("map", func(b *testing.B) {
b.SetBytes(int64(len(doc)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := map[string]interface{}{}
err := toml.Unmarshal(doc, &d)
if err != nil {
panic(err)
}
}
})
})
b.Run("ReferenceFile", func(b *testing.B) {
bytes, err := ioutil.ReadFile("benchmark.toml")
if err != nil {
b.Fatal(err)
}
b.Run("struct", func(b *testing.B) {
b.SetBytes(int64(len(bytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := benchmarkDoc{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
}
})
b.Run("map", func(b *testing.B) {
b.SetBytes(int64(len(bytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := map[string]interface{}{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
}
})
})
b.Run("HugoFrontMatter", func(b *testing.B) {
b.SetBytes(int64(len(hugoFrontMatterbytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := map[string]interface{}{}
err := toml.Unmarshal(hugoFrontMatterbytes, &d)
if err != nil {
panic(err)
}
}
})
}
func marshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
enc := toml.NewEncoder(&b)
err := enc.Encode(v)
return b.Bytes(), err
}
func BenchmarkMarshal(b *testing.B) {
b.Run("SimpleDocument", func(b *testing.B) {
doc := []byte(`A = "hello"`)
b.Run("struct", func(b *testing.B) {
d := struct {
A string
}{}
@@ -38,21 +125,114 @@ func BenchmarkUnmarshalSimple(b *testing.B) {
if err != nil {
panic(err)
}
}
})
b.Run("map", func(b *testing.B) {
b.SetBytes(int64(len(doc)))
b.ReportAllocs()
b.ResetTimer()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var out []byte
for i := 0; i < b.N; i++ {
out, err = marshal(d)
if err != nil {
panic(err)
}
}
b.SetBytes(int64(len(out)))
})
b.Run("map", func(b *testing.B) {
d := map[string]interface{}{}
err := toml.Unmarshal(doc, &d)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
var out []byte
for i := 0; i < b.N; i++ {
out, err = marshal(d)
if err != nil {
panic(err)
}
}
b.SetBytes(int64(len(out)))
})
})
b.Run("ReferenceFile", func(b *testing.B) {
bytes, err := ioutil.ReadFile("benchmark.toml")
if err != nil {
b.Fatal(err)
}
b.Run("struct", func(b *testing.B) {
d := benchmarkDoc{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
var out []byte
for i := 0; i < b.N; i++ {
out, err = marshal(d)
if err != nil {
panic(err)
}
}
b.SetBytes(int64(len(out)))
})
b.Run("map", func(b *testing.B) {
d := map[string]interface{}{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
var out []byte
for i := 0; i < b.N; i++ {
out, err = marshal(d)
if err != nil {
panic(err)
}
}
b.SetBytes(int64(len(out)))
})
})
b.Run("HugoFrontMatter", func(b *testing.B) {
d := map[string]interface{}{}
err := toml.Unmarshal(hugoFrontMatterbytes, &d)
if err != nil {
panic(err)
}
b.ReportAllocs()
b.ResetTimer()
var out []byte
for i := 0; i < b.N; i++ {
out, err = marshal(d)
if err != nil {
panic(err)
}
}
b.SetBytes(int64(len(out)))
})
}
@@ -163,40 +343,7 @@ type benchmarkDoc struct {
}
}
func BenchmarkReferenceFile(b *testing.B) {
bytes, err := ioutil.ReadFile("benchmark.toml")
if err != nil {
b.Fatal(err)
}
b.Run("struct", func(b *testing.B) {
b.SetBytes(int64(len(bytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := benchmarkDoc{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
}
})
b.Run("map", func(b *testing.B) {
b.SetBytes(int64(len(bytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := map[string]interface{}{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
}
})
}
func TestReferenceFile(t *testing.T) {
func TestUnmarshalReferenceFile(t *testing.T) {
bytes, err := ioutil.ReadFile("benchmark.toml")
require.NoError(t, err)
d := benchmarkDoc{}
@@ -483,8 +630,7 @@ trimmed in raw strings.
require.Equal(t, expected, d)
}
func BenchmarkHugoFrontMatter(b *testing.B) {
bytes := []byte(`
var hugoFrontMatterbytes = []byte(`
categories = ["Development", "VIM"]
date = "2012-04-06"
description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
@@ -506,14 +652,3 @@ show_comments = false
[cascade._target]
kind = "section"
`)
b.SetBytes(int64(len(bytes)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
d := map[string]interface{}{}
err := toml.Unmarshal(bytes, &d)
if err != nil {
panic(err)
}
}
}
+11 -4
View File
@@ -204,11 +204,18 @@ def printtable(data):
</table>""")
fold = 3
printtable(results[:fold])
def match(x):
return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0]
above = [x for x in results if match(x)]
below = [x for x in results if not match(x)]
printtable(above)
print("<details><summary>See more</summary>")
print('<p>The table above has the results of the most common use-cases. The table below contains the results of all benchmarks, including unrealistic ones. is provided for completeness.</p>')
printtable(results[fold:])
print("""<p>The table above has the results of the most common use-cases. The table below
contains the results of all benchmarks, including unrealistic ones. It is
provided for completeness.</p>""")
printtable(below)
print('<p>This table can be generated with <code>./ci.sh benchmark -a -html</code>.</p>')
print("</details>")
+3
View File
@@ -641,6 +641,9 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
}
func willConvertToTable(ctx encoderCtx, v reflect.Value) bool {
if !v.IsValid() {
return false
}
if v.Type() == timeType || v.Type().Implements(textMarshalerType) {
return false
}
+5 -1
View File
@@ -290,7 +290,11 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
return v, nil
case reflect.Slice:
elem := reflect.New(v.Type().Elem()).Elem()
elemType := v.Type().Elem()
if elemType.Kind() == reflect.Interface {
elemType = mapStringInterfaceType
}
elem := reflect.New(elemType).Elem()
elem2, err := d.handleArrayTable(key, elem)
if err != nil {
return reflect.Value{}, err
+15 -1
View File
@@ -688,7 +688,7 @@ B = "data"`,
"Name": "Hammer",
"Sku": int64(738594937),
},
nil,
map[string]interface{}(nil),
map[string]interface{}{
"Name": "Nail",
"Sku": int64(284758393),
@@ -1201,6 +1201,20 @@ B = "data"`,
}
},
},
{
desc: "empty array table in interface{}",
input: `[[products]]`,
gen: func() test {
return test{
target: &map[string]interface{}{},
expected: &map[string]interface{}{
"products": []interface{}{
map[string]interface{}(nil),
},
},
}
},
},
{
desc: "into map with invalid key type",
input: `A = "hello"`,