diff options
| author | Joe Tsai <joetsai@digital-static.net> | 2017-11-09 17:33:12 -0800 |
|---|---|---|
| committer | Joe Tsai <thebrokentoaster@gmail.com> | 2018-03-03 00:08:09 +0000 |
| commit | f0756ca2ea3a0dbd6c6479eacffd0023416280cb (patch) | |
| tree | e2c54637ebf6857ec30240aaa6f3b8d88406d94a /src/encoding/json/encode.go | |
| parent | e658b85f26f1de6f49578b7dac95eee0da880ab9 (diff) | |
| download | go-f0756ca2ea3a0dbd6c6479eacffd0023416280cb.tar.xz | |
encoding/json: use sync.Map for field cache
The previous type cache is quadratic in time in the situation where
new types are continually encountered. Now that it is possible to dynamically
create new types with the reflect package, this can cause json to
perform very poorly.
Switch to sync.Map which does well when the cache has hit steady state,
but also handles occasional updates in better than quadratic time.
benchmark old ns/op new ns/op delta
BenchmarkTypeFieldsCache/MissTypes1-8 14817 16202 +9.35%
BenchmarkTypeFieldsCache/MissTypes10-8 70926 69144 -2.51%
BenchmarkTypeFieldsCache/MissTypes100-8 976467 208973 -78.60%
BenchmarkTypeFieldsCache/MissTypes1000-8 79520162 1750371 -97.80%
BenchmarkTypeFieldsCache/MissTypes10000-8 6873625837 16847806 -99.75%
BenchmarkTypeFieldsCache/HitTypes1000-8 7.51 8.80 +17.18%
BenchmarkTypeFieldsCache/HitTypes10000-8 7.58 8.68 +14.51%
The old implementation takes 12 minutes just to build a cache of size 1e5
due to the quadratic behavior. I did not bother benchmark sizes above that.
Change-Id: I5e6facc1eb8e1b80e5ca285e4dd2cc8815618dad
Reviewed-on: https://go-review.googlesource.com/76850
Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/encoding/json/encode.go')
| -rw-r--r-- | src/encoding/json/encode.go | 32 |
1 files changed, 5 insertions, 27 deletions
diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 68512d0225..e7e7c4b7ef 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -21,7 +21,6 @@ import ( "strconv" "strings" "sync" - "sync/atomic" "unicode" "unicode/utf8" ) @@ -1258,34 +1257,13 @@ func dominantField(fields []field) (field, bool) { 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) } |
