250e073408
* Benchmark script * Rewrite unmarshaler using the stack Instead of tracking the build chain using `target`s, use the stack instead. Working and most benchmarks look good, but regression on structs unmarshalling. ~60% slower on ReferenceFile/struct. * Shortcut to check if last node of iterator * Remove unecessary pointer allocation * Skip over unused keys without marking them as seen * Add some tests * Fix mktemp on macos
520 lines
8.8 KiB
Go
520 lines
8.8 KiB
Go
package benchmark_test
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestUnmarshalSimple(t *testing.T) {
|
|
doc := []byte(`A = "hello"`)
|
|
d := struct {
|
|
A string
|
|
}{}
|
|
|
|
err := toml.Unmarshal(doc, &d)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUnmarshalSimple(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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
type benchmarkDoc struct {
|
|
Table struct {
|
|
Key string
|
|
Subtable struct {
|
|
Key string
|
|
}
|
|
Inline struct {
|
|
Name struct {
|
|
First string
|
|
Last string
|
|
}
|
|
Point struct {
|
|
X int64
|
|
Y int64
|
|
}
|
|
}
|
|
}
|
|
String struct {
|
|
Basic struct {
|
|
Basic string
|
|
}
|
|
Multiline struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
Continued struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
}
|
|
}
|
|
Literal struct {
|
|
Winpath string
|
|
Winpath2 string
|
|
Quoted string
|
|
Regex string
|
|
Multiline struct {
|
|
Regex2 string
|
|
Lines string
|
|
}
|
|
}
|
|
}
|
|
Integer struct {
|
|
Key1 int64
|
|
Key2 int64
|
|
Key3 int64
|
|
Key4 int64
|
|
Underscores struct {
|
|
Key1 int64
|
|
Key2 int64
|
|
Key3 int64
|
|
}
|
|
}
|
|
Float struct {
|
|
Fractional struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}
|
|
Exponent struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}
|
|
Both struct {
|
|
Key float64
|
|
}
|
|
Underscores struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
}
|
|
}
|
|
Boolean struct {
|
|
True bool
|
|
False bool
|
|
}
|
|
Datetime struct {
|
|
Key1 time.Time
|
|
Key2 time.Time
|
|
Key3 time.Time
|
|
}
|
|
Array struct {
|
|
Key1 []int64
|
|
Key2 []string
|
|
Key3 [][]int64
|
|
// TODO: Key4 not supported by go-toml's Unmarshal
|
|
Key4 []interface{}
|
|
Key5 []int64
|
|
Key6 []int64
|
|
}
|
|
Products []struct {
|
|
Name string
|
|
Sku int64
|
|
Color string
|
|
}
|
|
Fruit []struct {
|
|
Name string
|
|
Physical struct {
|
|
Color string
|
|
Shape string
|
|
}
|
|
Variety []struct {
|
|
Name string
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
bytes, err := ioutil.ReadFile("benchmark.toml")
|
|
require.NoError(t, err)
|
|
d := benchmarkDoc{}
|
|
err = toml.Unmarshal(bytes, &d)
|
|
require.NoError(t, err)
|
|
|
|
expected := benchmarkDoc{
|
|
Table: struct {
|
|
Key string
|
|
Subtable struct{ Key string }
|
|
Inline struct {
|
|
Name struct {
|
|
First string
|
|
Last string
|
|
}
|
|
Point struct {
|
|
X int64
|
|
Y int64
|
|
}
|
|
}
|
|
}{
|
|
Key: "value",
|
|
Subtable: struct{ Key string }{
|
|
Key: "another value",
|
|
},
|
|
// note: x.y.z.w is purposefully missing
|
|
Inline: struct {
|
|
Name struct {
|
|
First string
|
|
Last string
|
|
}
|
|
Point struct {
|
|
X int64
|
|
Y int64
|
|
}
|
|
}{
|
|
Name: struct {
|
|
First string
|
|
Last string
|
|
}{
|
|
First: "Tom",
|
|
Last: "Preston-Werner",
|
|
},
|
|
Point: struct {
|
|
X int64
|
|
Y int64
|
|
}{
|
|
X: 1,
|
|
Y: 2,
|
|
},
|
|
},
|
|
},
|
|
String: struct {
|
|
Basic struct{ Basic string }
|
|
Multiline struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
Continued struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
}
|
|
}
|
|
Literal struct {
|
|
Winpath string
|
|
Winpath2 string
|
|
Quoted string
|
|
Regex string
|
|
Multiline struct {
|
|
Regex2 string
|
|
Lines string
|
|
}
|
|
}
|
|
}{
|
|
Basic: struct{ Basic string }{
|
|
Basic: "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF.",
|
|
},
|
|
Multiline: struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
Continued struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
}
|
|
}{
|
|
Key1: "One\nTwo",
|
|
Key2: "One\nTwo",
|
|
Key3: "One\nTwo",
|
|
|
|
Continued: struct {
|
|
Key1 string
|
|
Key2 string
|
|
Key3 string
|
|
}{
|
|
Key1: `The quick brown fox jumps over the lazy dog.`,
|
|
Key2: `The quick brown fox jumps over the lazy dog.`,
|
|
Key3: `The quick brown fox jumps over the lazy dog.`,
|
|
},
|
|
},
|
|
Literal: struct {
|
|
Winpath string
|
|
Winpath2 string
|
|
Quoted string
|
|
Regex string
|
|
Multiline struct {
|
|
Regex2 string
|
|
Lines string
|
|
}
|
|
}{
|
|
Winpath: `C:\Users\nodejs\templates`,
|
|
Winpath2: `\\ServerX\admin$\system32\`,
|
|
Quoted: `Tom "Dubs" Preston-Werner`,
|
|
Regex: `<\i\c*\s*>`,
|
|
|
|
Multiline: struct {
|
|
Regex2 string
|
|
Lines string
|
|
}{
|
|
Regex2: `I [dw]on't need \d{2} apples`,
|
|
Lines: `The first newline is
|
|
trimmed in raw strings.
|
|
All other whitespace
|
|
is preserved.
|
|
`,
|
|
},
|
|
},
|
|
},
|
|
Integer: struct {
|
|
Key1 int64
|
|
Key2 int64
|
|
Key3 int64
|
|
Key4 int64
|
|
Underscores struct {
|
|
Key1 int64
|
|
Key2 int64
|
|
Key3 int64
|
|
}
|
|
}{
|
|
Key1: 99,
|
|
Key2: 42,
|
|
Key3: 0,
|
|
Key4: -17,
|
|
|
|
Underscores: struct {
|
|
Key1 int64
|
|
Key2 int64
|
|
Key3 int64
|
|
}{
|
|
Key1: 1000,
|
|
Key2: 5349221,
|
|
Key3: 12345,
|
|
},
|
|
},
|
|
Float: struct {
|
|
Fractional struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}
|
|
Exponent struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}
|
|
Both struct{ Key float64 }
|
|
Underscores struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
}
|
|
}{
|
|
Fractional: struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}{
|
|
Key1: 1.0,
|
|
Key2: 3.1415,
|
|
Key3: -0.01,
|
|
},
|
|
Exponent: struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
Key3 float64
|
|
}{
|
|
Key1: 5e+22,
|
|
Key2: 1e6,
|
|
Key3: -2e-2,
|
|
},
|
|
Both: struct{ Key float64 }{
|
|
Key: 6.626e-34,
|
|
},
|
|
Underscores: struct {
|
|
Key1 float64
|
|
Key2 float64
|
|
}{
|
|
Key1: 9224617.445991228313,
|
|
Key2: 1e100,
|
|
},
|
|
},
|
|
Boolean: struct {
|
|
True bool
|
|
False bool
|
|
}{
|
|
True: true,
|
|
False: false,
|
|
},
|
|
Datetime: struct {
|
|
Key1 time.Time
|
|
Key2 time.Time
|
|
Key3 time.Time
|
|
}{
|
|
Key1: time.Date(1979, 5, 27, 7, 32, 0, 0, time.UTC),
|
|
Key2: time.Date(1979, 5, 27, 0, 32, 0, 0, time.FixedZone("", -7*3600)),
|
|
Key3: time.Date(1979, 5, 27, 0, 32, 0, 999999000, time.FixedZone("", -7*3600)),
|
|
},
|
|
Array: struct {
|
|
Key1 []int64
|
|
Key2 []string
|
|
Key3 [][]int64
|
|
Key4 []interface{}
|
|
Key5 []int64
|
|
Key6 []int64
|
|
}{
|
|
Key1: []int64{1, 2, 3},
|
|
Key2: []string{"red", "yellow", "green"},
|
|
Key3: [][]int64{{1, 2}, {3, 4, 5}},
|
|
Key4: []interface{}{
|
|
[]interface{}{int64(1), int64(2)},
|
|
[]interface{}{"a", "b", "c"},
|
|
},
|
|
Key5: []int64{1, 2, 3},
|
|
Key6: []int64{1, 2},
|
|
},
|
|
Products: []struct {
|
|
Name string
|
|
Sku int64
|
|
Color string
|
|
}{
|
|
{
|
|
Name: "Hammer",
|
|
Sku: 738594937,
|
|
},
|
|
{},
|
|
{
|
|
Name: "Nail",
|
|
Sku: 284758393,
|
|
Color: "gray",
|
|
},
|
|
},
|
|
Fruit: []struct {
|
|
Name string
|
|
Physical struct {
|
|
Color string
|
|
Shape string
|
|
}
|
|
Variety []struct{ Name string }
|
|
}{
|
|
{
|
|
Name: "apple",
|
|
Physical: struct {
|
|
Color string
|
|
Shape string
|
|
}{
|
|
Color: "red",
|
|
Shape: "round",
|
|
},
|
|
Variety: []struct{ Name string }{
|
|
{Name: "red delicious"},
|
|
{Name: "granny smith"},
|
|
},
|
|
},
|
|
{
|
|
Name: "banana",
|
|
Variety: []struct{ Name string }{
|
|
{Name: "plantain"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
require.Equal(t, expected, d)
|
|
}
|
|
|
|
func BenchmarkHugoFrontMatter(b *testing.B) {
|
|
bytes := []byte(`
|
|
categories = ["Development", "VIM"]
|
|
date = "2012-04-06"
|
|
description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim."
|
|
slug = "spf13-vim-3-0-release-and-new-website"
|
|
tags = [".vimrc", "plugins", "spf13-vim", "vim"]
|
|
title = "spf13-vim 3.0 release and new website"
|
|
include_toc = true
|
|
show_comments = false
|
|
|
|
[[cascade]]
|
|
background = "yosemite.jpg"
|
|
[cascade._target]
|
|
kind = "page"
|
|
lang = "en"
|
|
path = "/blog/**"
|
|
|
|
[[cascade]]
|
|
background = "goldenbridge.jpg"
|
|
[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)
|
|
}
|
|
}
|
|
}
|