Compare commits

...

6 Commits

Author SHA1 Message Date
Thomas Pelletier dc72d75f3e Keep separate fn for []interface{} unmarshal 2021-11-13 19:20:20 -05:00
Thomas Pelletier f77775b59e Use less reflection when making slices
```
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)
```
2021-11-13 19:20:20 -05:00
Thomas Pelletier b52f6c9823 Remove some allocs for slices in interfaces
```
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)
```
2021-11-13 19:20:20 -05:00
Thomas Pelletier 12244064bb Use global cache to unmarshal all slice types 2021-11-13 19:20:20 -05:00
Thomas Pelletier 6430ee0bfa Generic slice unmarshal fn 2021-11-13 19:20:20 -05:00
Thomas Pelletier cf530eba46 Specialize array unmarshal into []interface{} 2021-11-13 19:20:19 -05:00
5 changed files with 210 additions and 49 deletions
-1
View File
@@ -321,7 +321,6 @@ type benchmarkDoc struct {
Key1 []int64 Key1 []int64
Key2 []string Key2 []string
Key3 [][]int64 Key3 [][]int64
// TODO: Key4 not supported by go-toml's Unmarshal
Key4 []interface{} Key4 []interface{}
Key5 []int64 Key5 []int64
Key6 []int64 Key6 []int64
+11
View File
@@ -63,3 +63,14 @@ func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer {
// https://github.com/golang/go/issues/40481 // https://github.com/golang/go/issues/40481
return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) 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
}
+20
View File
@@ -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,
}
}
+30
View File
@@ -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
}
+146 -45
View File
@@ -11,6 +11,7 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
"unsafe"
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
@@ -620,62 +621,128 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
} }
} }
func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { type unmarshalArrayFn func(d *decoder, array *ast.Node, v reflect.Value) error
switch v.Kind() {
case reflect.Slice: var globalUnmarshalArrayFnCache atomic.Value // map[danger.TypeID]unmarshalArrayFn
if v.IsNil() {
v.Set(reflect.MakeSlice(v.Type(), 0, 16)) func unmarshalArrayFnForSlice(vt reflect.Type) unmarshalArrayFn {
} else { tid := danger.MakeTypeID(vt)
v.SetLen(0)
} cache, _ := globalUnmarshalArrayFnCache.Load().(map[danger.TypeID]unmarshalArrayFn)
case reflect.Array: fn, ok := cache[tid]
// arrays are always initialized
case reflect.Interface: if ok {
elem := v.Elem() return fn
if !elem.IsValid() {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if elem.Kind() == reflect.Slice {
if elem.Type() != sliceInterfaceType {
elem = reflect.New(sliceInterfaceType).Elem()
elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16))
} else if !elem.CanSet() {
nelem := reflect.New(sliceInterfaceType).Elem()
nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap()))
reflect.Copy(nelem, elem)
elem = nelem
}
}
err := d.unmarshalArray(array, elem)
if err != nil {
return err
}
v.Set(elem)
return nil
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 := v.Type().Elem() elemType := vt.Elem()
elemSize := elemType.Size()
fn = func(d *decoder, array *ast.Node, v reflect.Value) error {
sp := (*danger.Slice)(unsafe.Pointer(v.UnsafeAddr()))
sp.Len = 0
it := array.Children() it := array.Children()
idx := 0
for it.Next() { for it.Next() {
n := it.Node() n := it.Node()
// TODO: optimize idx := sp.Len
if v.Kind() == reflect.Slice {
elem := reflect.New(elemType).Elem() if sp.Len == sp.Cap {
c := sp.Cap
if c == 0 {
c = 16
} 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) err := d.handleValue(n, elem)
if err != nil { if err != nil {
return err return err
} }
v.Set(reflect.Append(v, elem)) sp.Len++
} else { // array }
if sp.Data == nil {
*sp = danger.ExtendSlice(vt, sp, 0)
}
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 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 {
switch v.Kind() {
case reflect.Slice:
fn := unmarshalArrayFnForSlice(v.Type())
return fn(d, array, v)
case reflect.Array:
// arrays are always initialized
it := array.Children()
idx := 0
for it.Next() {
n := it.Node()
if idx >= v.Len() { if idx >= v.Len() {
return nil return nil
} }
@@ -686,6 +753,39 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
} }
idx++ 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)
elem = reflect.ValueOf(&s).Elem()
} else if !elem.CanSet() {
s := make([]interface{}, elem.Len(), elem.Cap())
nelem := reflect.ValueOf(&s).Elem()
reflect.Copy(nelem, elem)
elem = nelem
}
elemIsSliceInterface = true
}
var err error
if elemIsSliceInterface {
err = unmarshalArraySliceInterface(d, array, elem)
} else {
err = d.unmarshalArray(array, elem)
}
v.Set(elem)
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())
} }
return nil return nil
@@ -1078,9 +1178,10 @@ var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap
func structField(v reflect.Value, name string) (reflect.Value, bool) { func structField(v reflect.Value, name string) (reflect.Value, bool) {
t := v.Type() t := v.Type()
tid := danger.MakeTypeID(t)
cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap) cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap)
fieldPaths, ok := cache[danger.MakeTypeID(t)] fieldPaths, ok := cache[tid]
if !ok { if !ok {
fieldPaths = map[string][]int{} fieldPaths = map[string][]int{}
@@ -1092,7 +1193,7 @@ func structField(v reflect.Value, name string) (reflect.Value, bool) {
}) })
newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1) newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1)
newCache[danger.MakeTypeID(t)] = fieldPaths newCache[tid] = fieldPaths
for k, v := range cache { for k, v := range cache {
newCache[k] = v newCache[k] = v
} }