From cf530eba46ce32c3601104168c16841680ac690b Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 00:35:50 -0500 Subject: [PATCH 1/6] Specialize array unmarshal into []interface{} --- unmarshaler.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/unmarshaler.go b/unmarshaler.go index 722e47d0..23851115 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -11,6 +11,7 @@ import ( "strings" "sync/atomic" "time" + "unsafe" "github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/danger" @@ -620,15 +621,57 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { } } +func (d *decoder) unmarshalArrayFastSliceInterface(array *ast.Node, v reflect.Value) error { + ifaceType := v.Type().Elem() + + sp := (*[]interface{})(unsafe.Pointer(v.UnsafeAddr())) + s := *sp + + if s == nil { + s = make([]interface{}, 0, 16) + } else { + s = s[:0] + } + + var x interface{} + + it := array.Children() + for it.Next() { + n := it.Node() + + idx := len(s) + s = append(s, x) + + datap := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&s)).Data) + elemP := danger.Stride(datap, unsafe.Sizeof(x), idx) + elem := reflect.NewAt(ifaceType, elemP).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + } + + *sp = s + + return nil +} + func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { + var vt reflect.Type switch v.Kind() { case reflect.Slice: + vt = v.Type() + if vt == sliceInterfaceType { + return d.unmarshalArrayFastSliceInterface(array, v) + } if v.IsNil() { v.Set(reflect.MakeSlice(v.Type(), 0, 16)) } else { v.SetLen(0) } case reflect.Array: + vt = v.Type() // arrays are always initialized case reflect.Interface: elem := v.Elem() @@ -658,7 +701,7 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind()) } - elemType := v.Type().Elem() + elemType := vt.Elem() it := array.Children() idx := 0 From 6430ee0bfab6e0c44e9b5c0a9c544e9edd742f91 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 09:50:06 -0500 Subject: [PATCH 2/6] Generic slice unmarshal fn --- internal/danger/danger.go | 11 +++++++++ internal/danger/slices.go | 20 ++++++++++++++++ internal/danger/slices_optimized.go | 30 +++++++++++++++++++++++ unmarshaler.go | 37 ++++++++++++++++------------- 4 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 internal/danger/slices.go create mode 100644 internal/danger/slices_optimized.go diff --git a/internal/danger/danger.go b/internal/danger/danger.go index e38e1131..e6a633c4 100644 --- a/internal/danger/danger.go +++ b/internal/danger/danger.go @@ -63,3 +63,14 @@ func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer { // https://github.com/golang/go/issues/40481 return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) } + +type Slice struct { + Data unsafe.Pointer + Len int + Cap int +} + +type iface struct { + typ unsafe.Pointer + ptr unsafe.Pointer +} diff --git a/internal/danger/slices.go b/internal/danger/slices.go new file mode 100644 index 00000000..8bce8eff --- /dev/null +++ b/internal/danger/slices.go @@ -0,0 +1,20 @@ +//go:build go1.18 +// +build go1.18 + +package danger + +import ( + "reflect" + "unsafe" +) + +func ExtendSlice(t reflect.Type, s *Slice, n int) Slice { + arrayType := reflect.ArrayOf(n, t.Elem()) + arrayData := reflect.New(arrayType) + reflect.Copy(arrayData.Elem(), reflect.NewAt(t, unsafe.Pointer(s)).Elem()) + return Slice{ + Data: unsafe.Pointer(arrayData.Pointer()), + Len: s.Len, + Cap: n, + } +} diff --git a/internal/danger/slices_optimized.go b/internal/danger/slices_optimized.go new file mode 100644 index 00000000..3d796853 --- /dev/null +++ b/internal/danger/slices_optimized.go @@ -0,0 +1,30 @@ +//go:build !go1.18 +// +build !go1.18 + +package danger + +import ( + "reflect" + "unsafe" +) + +//go:linkname unsafe_NewArray reflect.unsafe_NewArray +func unsafe_NewArray(rtype unsafe.Pointer, length int) unsafe.Pointer + +//go:linkname typedslicecopy reflect.typedslicecopy +//go:noescape +func typedslicecopy(elemType unsafe.Pointer, dst, src Slice) int + +func ExtendSlice(t reflect.Type, s *Slice, n int) Slice { + elemTypeRef := t.Elem() + elemTypePtr := ((*iface)(unsafe.Pointer(&elemTypeRef))).ptr + + d := Slice{ + Data: unsafe_NewArray(elemTypePtr, n), + Len: s.Len, + Cap: n, + } + + typedslicecopy(elemTypePtr, d, *s) + return d +} diff --git a/unmarshaler.go b/unmarshaler.go index 23851115..49f6a977 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -622,37 +622,42 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { } func (d *decoder) unmarshalArrayFastSliceInterface(array *ast.Node, v reflect.Value) error { - ifaceType := v.Type().Elem() + vt := v.Type() - sp := (*[]interface{})(unsafe.Pointer(v.UnsafeAddr())) - s := *sp + elemType := vt.Elem() + elemSize := elemType.Size() - if s == nil { - s = make([]interface{}, 0, 16) - } else { - s = s[:0] - } + sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) - var x interface{} + sp.Len = 0 it := array.Children() for it.Next() { n := it.Node() - idx := len(s) - s = append(s, x) + idx := sp.Len - datap := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&s)).Data) - elemP := danger.Stride(datap, unsafe.Sizeof(x), idx) - elem := reflect.NewAt(ifaceType, elemP).Elem() + if sp.Len == sp.Cap { + c := sp.Cap + if c == 0 { + c = 10 + } else { + c *= 2 + } + *sp = danger.ExtendSlice(vt, sp, c) + } + + datap := unsafe.Pointer(sp.Data) + elemp := danger.Stride(datap, elemSize, idx) + elem := reflect.NewAt(elemType, elemp).Elem() err := d.handleValue(n, elem) if err != nil { return err } - } - *sp = s + sp.Len++ + } return nil } From 12244064bbf6f296d1c3d1515112ff452264809f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 10:16:29 -0500 Subject: [PATCH 3/6] Use global cache to unmarshal all slice types --- benchmark/benchmark_test.go | 1 - unmarshaler.go | 89 +++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/benchmark/benchmark_test.go b/benchmark/benchmark_test.go index a51476cd..fe3ce276 100644 --- a/benchmark/benchmark_test.go +++ b/benchmark/benchmark_test.go @@ -321,7 +321,6 @@ type benchmarkDoc struct { Key1 []int64 Key2 []string Key3 [][]int64 - // TODO: Key4 not supported by go-toml's Unmarshal Key4 []interface{} Key5 []int64 Key6 []int64 diff --git a/unmarshaler.go b/unmarshaler.go index 49f6a977..8251db27 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -621,45 +621,71 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { } } -func (d *decoder) unmarshalArrayFastSliceInterface(array *ast.Node, v reflect.Value) error { - vt := v.Type() +type unmarshalArrayFn func(d *decoder, array *ast.Node, v reflect.Value) error + +var globalUnmarshalArrayFnCache atomic.Value // map[danger.TypeID]unmarshalArrayFn + +func unmarshalArrayFnFor(vt reflect.Type) unmarshalArrayFn { + tid := danger.MakeTypeID(vt) + + cache, _ := globalUnmarshalArrayFnCache.Load().(map[danger.TypeID]unmarshalArrayFn) + fn, ok := cache[tid] + + if ok { + return fn + } elemType := vt.Elem() elemSize := elemType.Size() - sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) + fn = func(d *decoder, array *ast.Node, v reflect.Value) error { + sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) - sp.Len = 0 + sp.Len = 0 - it := array.Children() - for it.Next() { - n := it.Node() + it := array.Children() + for it.Next() { + n := it.Node() - idx := sp.Len + idx := sp.Len - if sp.Len == sp.Cap { - c := sp.Cap - if c == 0 { - c = 10 - } else { - c *= 2 + if sp.Len == sp.Cap { + c := sp.Cap + if c == 0 { + c = 16 + } else { + c *= 2 + } + *sp = danger.ExtendSlice(vt, sp, c) } - *sp = danger.ExtendSlice(vt, sp, c) - } - datap := unsafe.Pointer(sp.Data) - elemp := danger.Stride(datap, elemSize, idx) - elem := reflect.NewAt(elemType, elemp).Elem() + datap := unsafe.Pointer(sp.Data) + elemp := danger.Stride(datap, elemSize, idx) + elem := reflect.NewAt(elemType, elemp).Elem() - err := d.handleValue(n, elem) - if err != nil { - return err + err := d.handleValue(n, elem) + if err != nil { + return err + } + + sp.Len++ } - sp.Len++ + if sp.Data == nil { + *sp = danger.ExtendSlice(vt, sp, 0) + } + + return nil } - return nil + newCache := make(map[danger.TypeID]unmarshalArrayFn, len(cache)+1) + newCache[tid] = fn + for k, v := range cache { + newCache[k] = v + } + globalUnmarshalArrayFnCache.Store(newCache) + + return fn } func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { @@ -667,14 +693,8 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { switch v.Kind() { case reflect.Slice: vt = v.Type() - if vt == sliceInterfaceType { - return d.unmarshalArrayFastSliceInterface(array, v) - } - if v.IsNil() { - v.Set(reflect.MakeSlice(v.Type(), 0, 16)) - } else { - v.SetLen(0) - } + fn := unmarshalArrayFnFor(vt) + return fn(d, array, v) case reflect.Array: vt = v.Type() // arrays are always initialized @@ -1126,9 +1146,10 @@ var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap func structField(v reflect.Value, name string) (reflect.Value, bool) { t := v.Type() + tid := danger.MakeTypeID(t) cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap) - fieldPaths, ok := cache[danger.MakeTypeID(t)] + fieldPaths, ok := cache[tid] if !ok { fieldPaths = map[string][]int{} @@ -1140,7 +1161,7 @@ func structField(v reflect.Value, name string) (reflect.Value, bool) { }) newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1) - newCache[danger.MakeTypeID(t)] = fieldPaths + newCache[tid] = fieldPaths for k, v := range cache { newCache[k] = v } From b52f6c98233f35ce9a9007cd9933fd2fb7a408b2 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 11:16:41 -0500 Subject: [PATCH 4/6] Remove some allocs for slices in interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta UnmarshalDataset/config-2 24.9ms ± 1% 24.9ms ± 0% ~ (p=0.413 n=5+4) UnmarshalDataset/canada-2 66.1ms ± 0% 61.7ms ± 1% -6.63% (p=0.008 n=5+5) UnmarshalDataset/citm_catalog-2 25.3ms ± 5% 24.7ms ± 1% -2.09% (p=0.032 n=5+5) UnmarshalDataset/twitter-2 10.9ms ± 2% 10.9ms ± 2% ~ (p=1.000 n=5+5) UnmarshalDataset/code-2 108ms ± 0% 108ms ± 0% ~ (p=0.095 n=5+5) UnmarshalDataset/example-2 177µs ± 2% 176µs ± 0% ~ (p=0.841 n=5+5) Unmarshal/SimpleDocument/struct-2 579ns ± 0% 586ns ± 1% +1.30% (p=0.008 n=5+5) Unmarshal/SimpleDocument/map-2 875ns ± 1% 876ns ± 0% ~ (p=0.548 n=5+5) Unmarshal/ReferenceFile/struct-2 49.7µs ± 1% 49.5µs ± 0% ~ (p=0.095 n=5+5) Unmarshal/ReferenceFile/map-2 80.4µs ± 0% 79.6µs ± 0% -0.99% (p=0.008 n=5+5) Unmarshal/HugoFrontMatter-2 13.9µs ± 0% 13.7µs ± 0% -1.70% (p=0.008 n=5+5) name old speed new speed delta UnmarshalDataset/config-2 42.1MB/s ± 1% 42.2MB/s ± 0% ~ (p=0.381 n=5+4) UnmarshalDataset/canada-2 33.3MB/s ± 0% 35.7MB/s ± 1% +7.11% (p=0.008 n=5+5) UnmarshalDataset/citm_catalog-2 22.1MB/s ± 5% 22.6MB/s ± 1% +2.08% (p=0.032 n=5+5) UnmarshalDataset/twitter-2 40.7MB/s ± 2% 40.6MB/s ± 2% ~ (p=1.000 n=5+5) UnmarshalDataset/code-2 24.8MB/s ± 0% 24.9MB/s ± 0% ~ (p=0.103 n=5+5) UnmarshalDataset/example-2 45.8MB/s ± 2% 46.0MB/s ± 0% ~ (p=0.841 n=5+5) Unmarshal/SimpleDocument/struct-2 19.0MB/s ± 0% 18.8MB/s ± 1% -1.26% (p=0.008 n=5+5) Unmarshal/SimpleDocument/map-2 12.6MB/s ± 1% 12.6MB/s ± 0% ~ (p=0.508 n=5+5) Unmarshal/ReferenceFile/struct-2 105MB/s ± 1% 106MB/s ± 0% ~ (p=0.095 n=5+5) Unmarshal/ReferenceFile/map-2 65.2MB/s ± 0% 65.8MB/s ± 0% +1.00% (p=0.008 n=5+5) Unmarshal/HugoFrontMatter-2 39.3MB/s ± 0% 40.0MB/s ± 0% +1.73% (p=0.008 n=5+5) name old alloc/op new alloc/op delta UnmarshalDataset/config-2 5.85MB ± 0% 5.85MB ± 0% -0.00% (p=0.008 n=5+5) UnmarshalDataset/canada-2 76.6MB ± 0% 75.2MB ± 0% -1.76% (p=0.016 n=4+5) UnmarshalDataset/citm_catalog-2 35.3MB ± 0% 35.0MB ± 0% -0.71% (p=0.008 n=5+5) UnmarshalDataset/twitter-2 13.5MB ± 0% 13.5MB ± 0% -0.19% (p=0.016 n=4+5) UnmarshalDataset/code-2 22.3MB ± 0% 22.0MB ± 0% -1.31% (p=0.008 n=5+5) UnmarshalDataset/example-2 204kB ± 0% 203kB ± 0% -0.34% (p=0.008 n=5+5) Unmarshal/SimpleDocument/struct-2 709B ± 0% 709B ± 0% ~ (all equal) Unmarshal/SimpleDocument/map-2 1.08kB ± 0% 1.08kB ± 0% ~ (all equal) Unmarshal/ReferenceFile/struct-2 19.8kB ± 0% 19.7kB ± 0% -0.24% (p=0.008 n=5+5) Unmarshal/ReferenceFile/map-2 37.3kB ± 0% 37.0kB ± 0% -0.64% (p=0.029 n=4+4) Unmarshal/HugoFrontMatter-2 7.26kB ± 0% 7.22kB ± 0% -0.66% (p=0.008 n=5+5) name old allocs/op new allocs/op delta UnmarshalDataset/config-2 230k ± 0% 230k ± 0% -0.00% (p=0.000 n=5+4) UnmarshalDataset/canada-2 447k ± 0% 391k ± 0% -12.53% (p=0.008 n=5+5) UnmarshalDataset/citm_catalog-2 169k ± 0% 158k ± 0% -6.20% (p=0.029 n=4+4) UnmarshalDataset/twitter-2 55.8k ± 0% 54.7k ± 0% -1.88% (p=0.029 n=4+4) UnmarshalDataset/code-2 1.06M ± 0% 1.05M ± 0% -1.14% (p=0.008 n=5+5) UnmarshalDataset/example-2 1.31k ± 0% 1.28k ± 0% -2.21% (p=0.008 n=5+5) Unmarshal/SimpleDocument/struct-2 8.00 ± 0% 8.00 ± 0% ~ (all equal) Unmarshal/SimpleDocument/map-2 13.0 ± 0% 13.0 ± 0% ~ (all equal) Unmarshal/ReferenceFile/struct-2 125 ± 0% 123 ± 0% -1.60% (p=0.008 n=5+5) Unmarshal/ReferenceFile/map-2 600 ± 0% 590 ± 0% -1.67% (p=0.008 n=5+5) Unmarshal/HugoFrontMatter-2 132 ± 0% 130 ± 0% -1.52% (p=0.008 n=5+5) ``` --- unmarshaler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unmarshaler.go b/unmarshaler.go index 8251db27..92237c46 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -625,7 +625,7 @@ type unmarshalArrayFn func(d *decoder, array *ast.Node, v reflect.Value) error var globalUnmarshalArrayFnCache atomic.Value // map[danger.TypeID]unmarshalArrayFn -func unmarshalArrayFnFor(vt reflect.Type) unmarshalArrayFn { +func unmarshalArrayFnForSlice(vt reflect.Type) unmarshalArrayFn { tid := danger.MakeTypeID(vt) cache, _ := globalUnmarshalArrayFnCache.Load().(map[danger.TypeID]unmarshalArrayFn) @@ -693,7 +693,7 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { switch v.Kind() { case reflect.Slice: vt = v.Type() - fn := unmarshalArrayFnFor(vt) + fn := unmarshalArrayFnForSlice(vt) return fn(d, array, v) case reflect.Array: vt = v.Type() @@ -701,8 +701,8 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { case reflect.Interface: elem := v.Elem() if !elem.IsValid() { - elem = reflect.New(sliceInterfaceType).Elem() - elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + s := make([]interface{}, 0, 16) + elem = reflect.ValueOf(&s).Elem() } else if elem.Kind() == reflect.Slice { if elem.Type() != sliceInterfaceType { elem = reflect.New(sliceInterfaceType).Elem() From f77775b59edf3cebf8121335e39fcfe0d5025e6f Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 11:24:59 -0500 Subject: [PATCH 5/6] Use less reflection when making slices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta UnmarshalDataset/config-2 24.9ms ± 0% 24.6ms ± 0% -1.09% (p=0.029 n=4+4) UnmarshalDataset/canada-2 61.7ms ± 1% 62.1ms ± 3% ~ (p=1.000 n=5+5) UnmarshalDataset/citm_catalog-2 24.7ms ± 1% 24.2ms ± 0% -2.30% (p=0.008 n=5+5) UnmarshalDataset/twitter-2 10.9ms ± 2% 10.7ms ± 1% -1.46% (p=0.008 n=5+5) UnmarshalDataset/code-2 108ms ± 0% 106ms ± 0% -1.91% (p=0.008 n=5+5) UnmarshalDataset/example-2 176µs ± 0% 173µs ± 0% -1.83% (p=0.008 n=5+5) Unmarshal/SimpleDocument/struct-2 586ns ± 1% 587ns ± 0% ~ (p=0.690 n=5+5) Unmarshal/SimpleDocument/map-2 876ns ± 0% 872ns ± 0% ~ (p=0.095 n=5+5) Unmarshal/ReferenceFile/struct-2 49.5µs ± 0% 49.5µs ± 0% ~ (p=0.222 n=5+5) Unmarshal/ReferenceFile/map-2 79.6µs ± 0% 79.1µs ± 0% -0.62% (p=0.008 n=5+5) Unmarshal/HugoFrontMatter-2 13.7µs ± 0% 13.5µs ± 0% -0.91% (p=0.008 n=5+5) name old speed new speed delta UnmarshalDataset/config-2 42.2MB/s ± 0% 42.7MB/s ± 0% +1.10% (p=0.029 n=4+4) UnmarshalDataset/canada-2 35.7MB/s ± 1% 35.5MB/s ± 3% ~ (p=1.000 n=5+5) UnmarshalDataset/citm_catalog-2 22.6MB/s ± 1% 23.1MB/s ± 0% +2.36% (p=0.008 n=5+5) UnmarshalDataset/twitter-2 40.6MB/s ± 2% 41.2MB/s ± 1% +1.47% (p=0.008 n=5+5) UnmarshalDataset/code-2 24.9MB/s ± 0% 25.4MB/s ± 0% +1.95% (p=0.008 n=5+5) UnmarshalDataset/example-2 46.0MB/s ± 0% 46.9MB/s ± 0% +1.86% (p=0.008 n=5+5) Unmarshal/SimpleDocument/struct-2 18.8MB/s ± 1% 18.7MB/s ± 0% ~ (p=0.651 n=5+5) Unmarshal/SimpleDocument/map-2 12.6MB/s ± 0% 12.6MB/s ± 0% ~ (p=0.087 n=5+5) Unmarshal/ReferenceFile/struct-2 106MB/s ± 0% 106MB/s ± 0% ~ (p=0.222 n=5+5) Unmarshal/ReferenceFile/map-2 65.8MB/s ± 0% 66.2MB/s ± 0% +0.63% (p=0.008 n=5+5) Unmarshal/HugoFrontMatter-2 40.0MB/s ± 0% 40.3MB/s ± 0% +0.92% (p=0.008 n=5+5) name old alloc/op new alloc/op delta UnmarshalDataset/config-2 5.85MB ± 0% 5.85MB ± 0% ~ (p=1.000 n=5+5) UnmarshalDataset/canada-2 75.2MB ± 0% 75.2MB ± 0% ~ (p=1.000 n=5+5) UnmarshalDataset/citm_catalog-2 35.0MB ± 0% 35.0MB ± 0% ~ (p=0.841 n=5+5) UnmarshalDataset/twitter-2 13.5MB ± 0% 13.5MB ± 0% ~ (p=0.548 n=5+5) UnmarshalDataset/code-2 22.0MB ± 0% 22.0MB ± 0% ~ (p=0.738 n=5+5) UnmarshalDataset/example-2 203kB ± 0% 203kB ± 0% ~ (p=0.714 n=5+5) Unmarshal/SimpleDocument/struct-2 709B ± 0% 709B ± 0% ~ (all equal) Unmarshal/SimpleDocument/map-2 1.08kB ± 0% 1.08kB ± 0% ~ (all equal) Unmarshal/ReferenceFile/struct-2 19.7kB ± 0% 19.7kB ± 0% ~ (all equal) Unmarshal/ReferenceFile/map-2 37.0kB ± 0% 37.0kB ± 0% ~ (p=0.333 n=4+5) Unmarshal/HugoFrontMatter-2 7.22kB ± 0% 7.22kB ± 0% ~ (all equal) name old allocs/op new allocs/op delta UnmarshalDataset/config-2 230k ± 0% 230k ± 0% ~ (p=0.556 n=4+5) UnmarshalDataset/canada-2 391k ± 0% 391k ± 0% ~ (all equal) UnmarshalDataset/citm_catalog-2 158k ± 0% 158k ± 0% ~ (p=1.000 n=4+5) UnmarshalDataset/twitter-2 54.7k ± 0% 54.7k ± 0% ~ (p=1.000 n=4+5) UnmarshalDataset/code-2 1.05M ± 0% 1.05M ± 0% ~ (all equal) UnmarshalDataset/example-2 1.28k ± 0% 1.28k ± 0% ~ (all equal) Unmarshal/SimpleDocument/struct-2 8.00 ± 0% 8.00 ± 0% ~ (all equal) Unmarshal/SimpleDocument/map-2 13.0 ± 0% 13.0 ± 0% ~ (all equal) Unmarshal/ReferenceFile/struct-2 123 ± 0% 123 ± 0% ~ (all equal) Unmarshal/ReferenceFile/map-2 590 ± 0% 590 ± 0% ~ (all equal) Unmarshal/HugoFrontMatter-2 130 ± 0% 130 ± 0% ~ (all equal) ``` --- unmarshaler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unmarshaler.go b/unmarshaler.go index 92237c46..1307a337 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -705,11 +705,11 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { elem = reflect.ValueOf(&s).Elem() } else if elem.Kind() == reflect.Slice { if elem.Type() != sliceInterfaceType { - elem = reflect.New(sliceInterfaceType).Elem() - elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + s := make([]interface{}, 0, 16) + elem = reflect.ValueOf(&s).Elem() } else if !elem.CanSet() { - nelem := reflect.New(sliceInterfaceType).Elem() - nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) + s := make([]interface{}, elem.Len(), elem.Cap()) + nelem := reflect.ValueOf(&s).Elem() reflect.Copy(nelem, elem) elem = nelem } From dc72d75f3ec76fa4a824bcc52980591ba8685ab9 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 11 Nov 2021 11:50:46 -0500 Subject: [PATCH 6/6] Keep separate fn for []interface{} unmarshal --- unmarshaler.go | 108 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/unmarshaler.go b/unmarshaler.go index 1307a337..9c7ef526 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -688,21 +688,78 @@ func unmarshalArrayFnForSlice(vt reflect.Type) unmarshalArrayFn { return fn } +func unmarshalArraySliceInterface(d *decoder, array *ast.Node, v reflect.Value) error { + sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr())) + + sp.Len = 0 + + var x interface{} + + it := array.Children() + for it.Next() { + n := it.Node() + + idx := sp.Len + + if sp.Len == sp.Cap { + c := sp.Cap + if c == 0 { + c = 16 + } else { + c *= 2 + } + *sp = danger.ExtendSlice(sliceInterfaceType, sp, c) + } + + datap := unsafe.Pointer(sp.Data) + elemp := danger.Stride(datap, unsafe.Sizeof(x), idx) + elem := reflect.NewAt(sliceInterfaceType.Elem(), elemp).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + + sp.Len++ + } + + if sp.Data == nil { + *sp = danger.ExtendSlice(sliceInterfaceType, sp, 0) + } + + return nil +} + func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { - var vt reflect.Type switch v.Kind() { case reflect.Slice: - vt = v.Type() - fn := unmarshalArrayFnForSlice(vt) + fn := unmarshalArrayFnForSlice(v.Type()) return fn(d, array, v) case reflect.Array: - vt = v.Type() // arrays are always initialized + + it := array.Children() + idx := 0 + for it.Next() { + n := it.Node() + + if idx >= v.Len() { + return nil + } + elem := v.Index(idx) + err := d.handleValue(n, elem) + if err != nil { + return err + } + idx++ + } case reflect.Interface: + elemIsSliceInterface := false elem := v.Elem() if !elem.IsValid() { s := make([]interface{}, 0, 16) elem = reflect.ValueOf(&s).Elem() + elemIsSliceInterface = true } else if elem.Kind() == reflect.Slice { if elem.Type() != sliceInterfaceType { s := make([]interface{}, 0, 16) @@ -713,49 +770,24 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { reflect.Copy(nelem, elem) elem = nelem } + elemIsSliceInterface = true } - err := d.unmarshalArray(array, elem) - if err != nil { - return err + + var err error + if elemIsSliceInterface { + err = unmarshalArraySliceInterface(d, array, elem) + } else { + err = d.unmarshalArray(array, elem) } + v.Set(elem) - return nil + return err default: // TODO: use newDecodeError, but first the parser needs to fill // array.Data. return fmt.Errorf("toml: cannot store array in Go type %s", v.Kind()) } - elemType := vt.Elem() - - it := array.Children() - idx := 0 - for it.Next() { - n := it.Node() - - // TODO: optimize - if v.Kind() == reflect.Slice { - elem := reflect.New(elemType).Elem() - - err := d.handleValue(n, elem) - if err != nil { - return err - } - - v.Set(reflect.Append(v, elem)) - } else { // array - if idx >= v.Len() { - return nil - } - elem := v.Index(idx) - err := d.handleValue(n, elem) - if err != nil { - return err - } - idx++ - } - } - return nil }