diff --git a/README.md b/README.md index b2f3312a..5a03665a 100644 --- a/README.md +++ b/README.md @@ -156,14 +156,17 @@ Execution time speedup compared to other Go TOML libraries: Benchmarkgo-toml v1BurntSushi/toml - HugoFrontMatter2.6x2.2x - ReferenceFile/map2.8x3.0x - ReferenceFile/struct5.4x6.2x + Marshal/HugoFrontMatter1.9x1.9x + Marshal/ReferenceFile/map1.7x1.9x + Marshal/ReferenceFile/struct2.7x2.9x + Unmarshal/HugoFrontMatter2.9x2.4x + Unmarshal/ReferenceFile/map3.1x3.0x + Unmarshal/ReferenceFile/struct5.5x5.8x
See more -

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 +

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.

@@ -171,14 +174,16 @@ provided for completeness.

- - - - - - - - + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
UnmarshalSimple/map3.8x2.4x
UnmarshalSimple/struct5.4x3.1x
UnmarshalDataset/example2.8x2.0x
UnmarshalDataset/code1.8x2.2x
UnmarshalDataset/twitter2.5x1.8x
UnmarshalDataset/citm_catalog1.9x1.2x
UnmarshalDataset/config3.0x2.5x
[Geo mean]3.0x2.4x
Marshal/SimpleDocument/map1.8x2.4x
Marshal/SimpleDocument/struct2.7x3.5x
Unmarshal/SimpleDocument/map4.3x2.4x
Unmarshal/SimpleDocument/struct5.8x3.3x
UnmarshalDataset/example3.1x2.2x
UnmarshalDataset/code1.8x2.1x
UnmarshalDataset/twitter2.7x1.9x
UnmarshalDataset/citm_catalog1.8x1.2x
UnmarshalDataset/config3.4x2.8x
[Geo mean]2.8x2.5x

This table can be generated with ./ci.sh benchmark -a -html.

diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index 6d90571b..a51476cd 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -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.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("struct", func(b *testing.B) { - b.SetBytes(int64(len(doc))) + 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.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(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("map", func(b *testing.B) { - b.SetBytes(int64(len(doc))) + 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++ { - d := map[string]interface{}{} - err := toml.Unmarshal(doc, &d) + 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) - } - } -} diff --git a/ci.sh b/ci.sh index 22cda0ad..3918f531 100755 --- a/ci.sh +++ b/ci.sh @@ -204,11 +204,18 @@ def printtable(data): """) -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("
See more") -print('

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.

') -printtable(results[fold:]) +print("""

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.

""") +printtable(below) print('

This table can be generated with ./ci.sh benchmark -a -html.

') print("
") diff --git a/marshaler.go b/marshaler.go index 0d8ed9c5..3d87dab6 100644 --- a/marshaler.go +++ b/marshaler.go @@ -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 } diff --git a/unmarshaler.go b/unmarshaler.go index 6dcb5c5c..a3280454 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -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 diff --git a/unmarshaler_test.go b/unmarshaler_test.go index f3deb238..e14c9be0 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -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"`,