import (
"bytes"
"compress/gzip"
+ "fmt"
+ "internal/testenv"
"io/ioutil"
"os"
+ "reflect"
+ "runtime"
"strings"
+ "sync"
"testing"
)
}
})
}
+
+func BenchmarkTypeFieldsCache(b *testing.B) {
+ var maxTypes int = 1e6
+ if testenv.Builder() != "" {
+ maxTypes = 1e3 // restrict cache sizes on builders
+ }
+
+ // Dynamically generate many new types.
+ types := make([]reflect.Type, maxTypes)
+ fs := []reflect.StructField{{
+ Type: reflect.TypeOf(""),
+ Index: []int{0},
+ }}
+ for i := range types {
+ fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i)
+ types[i] = reflect.StructOf(fs)
+ }
+
+ // clearClear clears the cache. Other JSON operations, must not be running.
+ clearCache := func() {
+ fieldCache = sync.Map{}
+ }
+
+ // MissTypes tests the performance of repeated cache misses.
+ // This measures the time to rebuild a cache of size nt.
+ for nt := 1; nt <= maxTypes; nt *= 10 {
+ ts := types[:nt]
+ b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) {
+ nc := runtime.GOMAXPROCS(0)
+ for i := 0; i < b.N; i++ {
+ clearCache()
+ var wg sync.WaitGroup
+ for j := 0; j < nc; j++ {
+ wg.Add(1)
+ go func(j int) {
+ for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] {
+ cachedTypeFields(t)
+ }
+ wg.Done()
+ }(j)
+ }
+ wg.Wait()
+ }
+ })
+ }
+
+ // HitTypes tests the performance of repeated cache hits.
+ // This measures the average time of each cache lookup.
+ for nt := 1; nt <= maxTypes; nt *= 10 {
+ // Pre-warm a cache of size nt.
+ clearCache()
+ for _, t := range types[:nt] {
+ cachedTypeFields(t)
+ }
+ b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ cachedTypeFields(types[0])
+ }
+ })
+ })
+ }
+}
"strconv"
"strings"
"sync"
- "sync/atomic"
"unicode"
"unicode/utf8"
)
return fields[0], true
}
-var fieldCache struct {
- value atomic.Value // map[reflect.Type][]field
- mu sync.Mutex // used only by writers
-}
+var fieldCache sync.Map // map[reflect.Type][]field
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) []field {
- m, _ := fieldCache.value.Load().(map[reflect.Type][]field)
- f := m[t]
- if f != nil {
- return f
- }
-
- // Compute fields without lock.
- // Might duplicate effort but won't hold other computations back.
- f = typeFields(t)
- if f == nil {
- f = []field{}
+ if f, ok := fieldCache.Load(t); ok {
+ return f.([]field)
}
-
- fieldCache.mu.Lock()
- m, _ = fieldCache.value.Load().(map[reflect.Type][]field)
- newM := make(map[reflect.Type][]field, len(m)+1)
- for k, v := range m {
- newM[k] = v
- }
- newM[t] = f
- fieldCache.value.Store(newM)
- fieldCache.mu.Unlock()
- return f
+ f, _ := fieldCache.LoadOrStore(t, typeFields(t))
+ return f.([]field)
}