From 4a000a21a414d139727f616a8bb97f847b1b310b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 25 Jun 2017 13:05:13 -0700 Subject: [PATCH] Benchmark against other libraries (#175) BenchmarkUnmarshalToml-8 1000 1320117 ns/op 450932 B/op 15072 allocs/op BenchmarkUnmarshalBurntSushiToml-8 3000 402897 ns/op 82900 B/op 1761 allocs/op BenchmarkUnmarshalJson-8 20000 66092 ns/op 3536 B/op 101 allocs/op BenchmarkUnmarshalYaml-8 10000 189600 ns/op 44872 B/op 1058 allocs/op --- benchmark.json | 164 +++++++++++++++++++++++++++++++ benchmark.sh | 4 +- benchmark.toml | 244 ++++++++++++++++++++++++++++++++++++++++++++++ benchmark.yml | 121 +++++++++++++++++++++++ benchmark_test.go | 192 ++++++++++++++++++++++++++++++++++++ marshal.go | 11 ++- test.sh | 2 + 7 files changed, 733 insertions(+), 5 deletions(-) create mode 100644 benchmark.json create mode 100644 benchmark.toml create mode 100644 benchmark.yml create mode 100644 benchmark_test.go diff --git a/benchmark.json b/benchmark.json new file mode 100644 index 0000000..86f99c6 --- /dev/null +++ b/benchmark.json @@ -0,0 +1,164 @@ +{ + "array": { + "key1": [ + 1, + 2, + 3 + ], + "key2": [ + "red", + "yellow", + "green" + ], + "key3": [ + [ + 1, + 2 + ], + [ + 3, + 4, + 5 + ] + ], + "key4": [ + [ + 1, + 2 + ], + [ + "a", + "b", + "c" + ] + ], + "key5": [ + 1, + 2, + 3 + ], + "key6": [ + 1, + 2 + ] + }, + "boolean": { + "False": false, + "True": true + }, + "datetime": { + "key1": "1979-05-27T07:32:00Z", + "key2": "1979-05-27T00:32:00-07:00", + "key3": "1979-05-27T00:32:00.999999-07:00" + }, + "float": { + "both": { + "key": 6.626e-34 + }, + "exponent": { + "key1": 5e+22, + "key2": 1000000, + "key3": -0.02 + }, + "fractional": { + "key1": 1, + "key2": 3.1415, + "key3": -0.01 + }, + "underscores": { + "key1": 9224617.445991227, + "key2": 1e+100 + } + }, + "fruit": [{ + "name": "apple", + "physical": { + "color": "red", + "shape": "round" + }, + "variety": [{ + "name": "red delicious" + }, + { + "name": "granny smith" + } + ] + }, + { + "name": "banana", + "variety": [{ + "name": "plantain" + }] + } + ], + "integer": { + "key1": 99, + "key2": 42, + "key3": 0, + "key4": -17, + "underscores": { + "key1": 1000, + "key2": 5349221, + "key3": 12345 + } + }, + "products": [{ + "name": "Hammer", + "sku": 738594937 + }, + {}, + { + "color": "gray", + "name": "Nail", + "sku": 284758393 + } + ], + "string": { + "basic": { + "basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." + }, + "literal": { + "multiline": { + "lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n", + "regex2": "I [dw]on't need \\d{2} apples" + }, + "quoted": "Tom \"Dubs\" Preston-Werner", + "regex": "\u003c\\i\\c*\\s*\u003e", + "winpath": "C:\\Users\\nodejs\\templates", + "winpath2": "\\\\ServerX\\admin$\\system32\\" + }, + "multiline": { + "continued": { + "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." + }, + "key1": "One\nTwo", + "key2": "One\nTwo", + "key3": "One\nTwo" + } + }, + "table": { + "inline": { + "name": { + "first": "Tom", + "last": "Preston-Werner" + }, + "point": { + "x": 1, + "y": 2 + } + }, + "key": "value", + "subtable": { + "key": "another value" + } + }, + "x": { + "y": { + "z": { + "w": {} + } + } + } +} diff --git a/benchmark.sh b/benchmark.sh index cf1dabb..8b8bb52 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -13,7 +13,7 @@ fi tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX` ref_tempdir="${tempdir}/ref" -ref_benchmark="${ref_tempdir}/benchmark-${reference_ref}.txt" +ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt" local_benchmark="`pwd`/benchmark-local.txt" echo "=== ${reference_ref} (${ref_tempdir})" @@ -25,7 +25,7 @@ popd >/dev/null echo "" echo "=== local" -go test -bench=. -benchmem | tee ${local_benchmark} +go test -bench=. -benchmem | tee ${local_benchmark} echo "" echo "=== diff" diff --git a/benchmark.toml b/benchmark.toml new file mode 100644 index 0000000..dfd77e0 --- /dev/null +++ b/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.yml b/benchmark.yml new file mode 100644 index 0000000..0bd19f0 --- /dev/null +++ b/benchmark.yml @@ -0,0 +1,121 @@ +--- +array: + key1: + - 1 + - 2 + - 3 + key2: + - red + - yellow + - green + key3: + - - 1 + - 2 + - - 3 + - 4 + - 5 + key4: + - - 1 + - 2 + - - a + - b + - c + key5: + - 1 + - 2 + - 3 + key6: + - 1 + - 2 +boolean: + 'False': false + 'True': true +datetime: + key1: '1979-05-27T07:32:00Z' + key2: '1979-05-27T00:32:00-07:00' + key3: '1979-05-27T00:32:00.999999-07:00' +float: + both: + key: 6.626e-34 + exponent: + key1: 5.0e+22 + key2: 1000000 + key3: -0.02 + fractional: + key1: 1 + key2: 3.1415 + key3: -0.01 + underscores: + key1: 9224617.445991227 + key2: 1.0e+100 +fruit: +- name: apple + physical: + color: red + shape: round + variety: + - name: red delicious + - name: granny smith +- name: banana + variety: + - name: plantain +integer: + key1: 99 + key2: 42 + key3: 0 + key4: -17 + underscores: + key1: 1000 + key2: 5349221 + key3: 12345 +products: +- name: Hammer + sku: 738594937 +- {} +- color: gray + name: Nail + sku: 284758393 +string: + basic: + basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF." + literal: + multiline: + lines: | + The first newline is + trimmed in raw strings. + All other whitespace + is preserved. + regex2: I [dw]on't need \d{2} apples + quoted: Tom "Dubs" Preston-Werner + regex: "<\\i\\c*\\s*>" + winpath: C:\Users\nodejs\templates + winpath2: "\\\\ServerX\\admin$\\system32\\" + multiline: + continued: + 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. + key1: |- + One + Two + key2: |- + One + Two + key3: |- + One + Two +table: + inline: + name: + first: Tom + last: Preston-Werner + point: + x: 1 + y: 2 + key: value + subtable: + key: another value +x: + y: + z: + w: {} diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..e1f209d --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,192 @@ +package toml + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "testing" + "time" + + burntsushi "github.com/BurntSushi/toml" + yaml "gopkg.in/yaml.v2" +) + +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 BenchmarkParseToml(b *testing.B) { + fileBytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := LoadReader(bytes.NewReader(fileBytes)) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUnmarshalToml(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + target := benchmarkDoc{} + err := Unmarshal(bytes, &target) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUnmarshalBurntSushiToml(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.toml") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + target := benchmarkDoc{} + err := burntsushi.Unmarshal(bytes, &target) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUnmarshalJson(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.json") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + target := benchmarkDoc{} + err := json.Unmarshal(bytes, &target) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkUnmarshalYaml(b *testing.B) { + bytes, err := ioutil.ReadFile("benchmark.yml") + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + target := benchmarkDoc{} + err := yaml.Unmarshal(bytes, &target) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/marshal.go b/marshal.go index 9bf6fb9..1a3176f 100644 --- a/marshal.go +++ b/marshal.go @@ -268,15 +268,20 @@ func valueFromTree(mtype reflect.Type, tval *Tree) (reflect.Value, error) { mtypef := mtype.Field(i) opts := tomlOptions(mtypef) if opts.include { - key := opts.name - exists := tval.Has(key) - if exists { + baseKey := opts.name + keysToTry := []string{baseKey, strings.ToLower(baseKey), strings.ToTitle(baseKey)} + for _, key := range keysToTry { + exists := tval.Has(key) + if !exists { + continue + } val := tval.Get(key) mvalf, err := valueFromToml(mtypef.Type, val) if err != nil { return mval, formatError(err, tval.GetPosition(key)) } mval.Field(i).Set(mvalf) + break } } } diff --git a/test.sh b/test.sh index d2d2aed..91a8896 100755 --- a/test.sh +++ b/test.sh @@ -27,6 +27,8 @@ go vet ./... go get github.com/pelletier/go-buffruneio go get github.com/davecgh/go-spew/spew +go get gopkg.in/yaml.v2 +go get github.com/BurntSushi/toml # get code for BurntSushi TOML validation # pinning all to 'HEAD' for version 0.3.x work (TODO: pin to commit hash when tests stabilize)