From 8a8d1233bb6b9afad122a2c66a54be3826642458 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 24 Mar 2021 22:15:12 -0400 Subject: [PATCH] First benchmark! ~/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 1607115 742.0 ns/op BenchmarkUnmarshalSimple/v1-8 307977 3915 ns/op BenchmarkUnmarshalSimple/bs-8 516754 2330 ns/op BenchmarkReferenceFile/v2-8 9604 129158 ns/op 111422 B/op 1381 allocs/op BenchmarkReferenceFile/v1-8 4521 263808 ns/op 130566 B/op 2649 allocs/op BenchmarkReferenceFile/bs-8 4070 296271 ns/op 80784 B/op 1729 allocs/op PASS ok github.com/pelletier/go-toml/v2/benchmark 8.139s --- benchmark/benchmark.toml | 244 ++++++++++++++++++++++++++++++++++++ benchmark/benchmark_test.go | 178 ++++++++++++++++++++++++++ benchmark/go.mod | 15 +++ benchmark/go.sum | 16 +++ go.mod | 2 +- scanner.go | 2 +- 6 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 benchmark/benchmark.toml create mode 100644 benchmark/benchmark_test.go create mode 100644 benchmark/go.mod create mode 100644 benchmark/go.sum diff --git a/benchmark/benchmark.toml b/benchmark/benchmark.toml new file mode 100644 index 0000000..dfd77e0 --- /dev/null +++ b/benchmark/benchmark.toml @@ -0,0 +1,244 @@ +################################################################################ +## Comment + +# Speak your mind with the hash symbol. They go from the symbol to the end of +# the line. + + +################################################################################ +## Table + +# Tables (also known as hash tables or dictionaries) are collections of +# key/value pairs. They appear in square brackets on a line by themselves. + +[table] + +key = "value" # Yeah, you can do this. + +# Nested tables are denoted by table names with dots in them. Name your tables +# whatever crap you please, just don't use #, ., [ or ]. + +[table.subtable] + +key = "another value" + +# You don't need to specify all the super-tables if you don't want to. TOML +# knows how to do it for you. + +# [x] you +# [x.y] don't +# [x.y.z] need these +[x.y.z.w] # for this to work + + +################################################################################ +## Inline Table + +# Inline tables provide a more compact syntax for expressing tables. They are +# especially useful for grouped data that can otherwise quickly become verbose. +# Inline tables are enclosed in curly braces `{` and `}`. No newlines are +# allowed between the curly braces unless they are valid within a value. + +[table.inline] + +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } + + +################################################################################ +## String + +# There are four ways to express strings: basic, multi-line basic, literal, and +# multi-line literal. All strings must contain only valid UTF-8 characters. + +[string.basic] + +basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF." + +[string.multiline] + +# The following strings are byte-for-byte equivalent: +key1 = "One\nTwo" +key2 = """One\nTwo""" +key3 = """ +One +Two""" + +[string.multiline.continued] + +# The following strings are byte-for-byte equivalent: +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.\ + """ + +[string.literal] + +# What you see is what you get. +winpath = 'C:\Users\nodejs\templates' +winpath2 = '\\ServerX\admin$\system32\' +quoted = 'Tom "Dubs" Preston-Werner' +regex = '<\i\c*\s*>' + + +[string.literal.multiline] + +regex2 = '''I [dw]on't need \d{2} apples''' +lines = ''' +The first newline is +trimmed in raw strings. + All other whitespace + is preserved. +''' + + +################################################################################ +## Integer + +# Integers are whole numbers. Positive numbers may be prefixed with a plus sign. +# Negative numbers are prefixed with a minus sign. + +[integer] + +key1 = +99 +key2 = 42 +key3 = 0 +key4 = -17 + +[integer.underscores] + +# For large numbers, you may use underscores to enhance readability. Each +# underscore must be surrounded by at least one digit. +key1 = 1_000 +key2 = 5_349_221 +key3 = 1_2_3_4_5 # valid but inadvisable + + +################################################################################ +## Float + +# A float consists of an integer part (which may be prefixed with a plus or +# minus sign) followed by a fractional part and/or an exponent part. + +[float.fractional] + +key1 = +1.0 +key2 = 3.1415 +key3 = -0.01 + +[float.exponent] + +key1 = 5e+22 +key2 = 1e6 +key3 = -2E-2 + +[float.both] + +key = 6.626e-34 + +[float.underscores] + +key1 = 9_224_617.445_991_228_313 +key2 = 1e1_00 + + +################################################################################ +## Boolean + +# Booleans are just the tokens you're used to. Always lowercase. + +[boolean] + +True = true +False = false + + +################################################################################ +## Datetime + +# Datetimes are RFC 3339 dates. + +[datetime] + +key1 = 1979-05-27T07:32:00Z +key2 = 1979-05-27T00:32:00-07:00 +key3 = 1979-05-27T00:32:00.999999-07:00 + + +################################################################################ +## Array + +# Arrays are square brackets with other primitives inside. Whitespace is +# ignored. Elements are separated by commas. Data types may not be mixed. + +[array] + +key1 = [ 1, 2, 3 ] +key2 = [ "red", "yellow", "green" ] +key3 = [ [ 1, 2 ], [3, 4, 5] ] +#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok + +# Arrays can also be multiline. So in addition to ignoring whitespace, arrays +# also ignore newlines between the brackets. Terminating commas are ok before +# the closing bracket. + +key5 = [ + 1, 2, 3 +] +key6 = [ + 1, + 2, # this is ok +] + + +################################################################################ +## Array of Tables + +# These can be expressed by using a table name in double brackets. Each table +# with the same double bracketed name will be an element in the array. The +# tables are inserted in the order encountered. + +[[products]] + +name = "Hammer" +sku = 738594937 + +[[products]] + +[[products]] + +name = "Nail" +sku = 284758393 +color = "gray" + + +# You can create nested arrays of tables as well. + +[[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + +[[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go new file mode 100644 index 0000000..5d3d2ed --- /dev/null +++ b/benchmark/benchmark_test.go @@ -0,0 +1,178 @@ +package benchmark_test + +import ( + "io/ioutil" + "testing" + "time" + + tomlbs "github.com/BurntSushi/toml" + tomlv1 "github.com/pelletier/go-toml-v1" + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" +) + +type runner struct { + name string + unmarshal func([]byte, interface{}) error +} + +var runners = []runner{ + {"v2", toml.Unmarshal}, + {"v1", tomlv1.Unmarshal}, + {"bs", tomlbs.Unmarshal}, +} + +func bench(b *testing.B, f func(r runner, b *testing.B)) { + for _, r := range runners { + b.Run(r.name, func(b *testing.B) { + f(r, b) + }) + } +} + +func BenchmarkUnmarshalSimple(b *testing.B) { + bench(b, func(r runner, b *testing.B) { + d := struct { + A string + }{} + doc := []byte(`A = "hello"`) + for i := 0; i < b.N; i++ { + err := r.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 + U 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 + 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) { + bench(b, func(r runner, b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + d := benchmarkDoc{} + err := r.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) +} diff --git a/benchmark/go.mod b/benchmark/go.mod new file mode 100644 index 0000000..9e09e18 --- /dev/null +++ b/benchmark/go.mod @@ -0,0 +1,15 @@ +module github.com/pelletier/go-toml/v2/benchmark + +go 1.16 + +replace github.com/pelletier/go-toml/v2 => ../ + +replace github.com/pelletier/go-toml-v1 => /home/thomas/src/github.com/pelletier/go-toml-v1 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/pelletier/go-toml v1.8.1 // indirect + github.com/pelletier/go-toml-v1 v0.0.0-00010101000000-000000000000 + github.com/pelletier/go-toml/v2 v2.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.7.0 +) diff --git a/benchmark/go.sum b/benchmark/go.sum new file mode 100644 index 0000000..bcc2949 --- /dev/null +++ b/benchmark/go.sum @@ -0,0 +1,16 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go.mod b/go.mod index 3825359..6745f0a 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/pelletier/go-toml/v2 -go 1.14 +go 1.15 require github.com/stretchr/testify v1.7.0 diff --git a/scanner.go b/scanner.go index 9ee67fa..ba857c6 100644 --- a/scanner.go +++ b/scanner.go @@ -65,7 +65,7 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { switch b[i] { case '\'': if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { - return b[:i+3], b[:i+3], nil + return b[:i+3], b[i+3:], nil } } }