]> Cypherpunks repositories - gostls13.git/commitdiff
reflect: use sync.Map instead of RWMutex for type caches
authorBryan C. Mills <bcmills@google.com>
Thu, 16 Feb 2017 23:11:07 +0000 (18:11 -0500)
committerBryan Mills <bcmills@google.com>
Thu, 27 Apr 2017 15:34:41 +0000 (15:34 +0000)
This provides a significant speedup when using reflection-heavy code
on many CPU cores, such as when marshaling or unmarshaling protocol
buffers.

updates #17973
updates #18177

name                       old time/op    new time/op     delta
Call                          239ns ±10%      245ns ± 7%       ~     (p=0.562 n=10+9)
Call-6                        201ns ±38%       48ns ±29%    -76.39%  (p=0.000 n=10+9)
Call-48                       133ns ± 8%       12ns ± 2%    -90.92%  (p=0.000 n=10+8)
CallArgCopy/size=128          169ns ±12%      197ns ± 2%    +16.35%  (p=0.000 n=10+7)
CallArgCopy/size=128-6        142ns ± 9%       34ns ± 7%    -76.10%  (p=0.000 n=10+9)
CallArgCopy/size=128-48       125ns ± 3%        9ns ± 7%    -93.01%  (p=0.000 n=8+8)
CallArgCopy/size=256          177ns ± 8%      197ns ± 5%    +11.24%  (p=0.000 n=10+9)
CallArgCopy/size=256-6        148ns ±11%       35ns ± 6%    -76.23%  (p=0.000 n=10+9)
CallArgCopy/size=256-48       127ns ± 4%        9ns ± 9%    -92.66%  (p=0.000 n=10+9)
CallArgCopy/size=1024         196ns ± 6%      228ns ± 7%    +16.09%  (p=0.000 n=10+9)
CallArgCopy/size=1024-6       143ns ± 6%       42ns ± 5%    -70.39%  (p=0.000 n=8+8)
CallArgCopy/size=1024-48      130ns ± 7%       10ns ± 1%    -91.99%  (p=0.000 n=10+8)
CallArgCopy/size=4096         330ns ± 9%      351ns ± 5%     +6.20%  (p=0.004 n=10+9)
CallArgCopy/size=4096-6       173ns ±14%       62ns ± 6%    -63.83%  (p=0.000 n=10+8)
CallArgCopy/size=4096-48      141ns ± 6%       15ns ± 6%    -89.59%  (p=0.000 n=10+8)
CallArgCopy/size=65536       7.71µs ±10%     7.74µs ±10%       ~     (p=0.859 n=10+9)
CallArgCopy/size=65536-6     1.33µs ± 4%     1.34µs ± 6%       ~     (p=0.720 n=10+9)
CallArgCopy/size=65536-48     347ns ± 2%      344ns ± 2%       ~     (p=0.202 n=10+9)
PtrTo                        30.2ns ±10%     41.3ns ±11%    +36.97%  (p=0.000 n=10+9)
PtrTo-6                       126ns ± 6%        7ns ±10%    -94.47%  (p=0.000 n=9+9)
PtrTo-48                     86.9ns ± 9%      1.7ns ± 9%    -98.08%  (p=0.000 n=10+9)
FieldByName1                 86.6ns ± 5%     87.3ns ± 7%       ~     (p=0.737 n=10+9)
FieldByName1-6               19.8ns ±10%     18.7ns ±10%       ~     (p=0.073 n=9+9)
FieldByName1-48              7.54ns ± 4%     7.74ns ± 5%     +2.55%  (p=0.023 n=9+9)
FieldByName2                 1.63µs ± 8%     1.70µs ± 4%     +4.13%  (p=0.020 n=9+9)
FieldByName2-6                481ns ± 6%      490ns ±10%       ~     (p=0.474 n=9+9)
FieldByName2-48               723ns ± 3%      736ns ± 2%     +1.76%  (p=0.045 n=8+8)
FieldByName3                 10.5µs ± 7%     10.8µs ± 7%       ~     (p=0.234 n=8+8)
FieldByName3-6               2.78µs ± 3%     2.94µs ±10%     +5.87%  (p=0.031 n=9+9)
FieldByName3-48              3.72µs ± 2%     3.91µs ± 5%     +4.91%  (p=0.003 n=9+9)
InterfaceBig                 10.8ns ± 5%     10.7ns ± 5%       ~     (p=0.849 n=9+9)
InterfaceBig-6               9.62ns ±81%     1.79ns ± 4%    -81.38%  (p=0.003 n=9+9)
InterfaceBig-48              0.48ns ±34%     0.50ns ± 7%       ~     (p=0.071 n=8+9)
InterfaceSmall               10.7ns ± 5%     10.9ns ± 4%       ~     (p=0.243 n=9+9)
InterfaceSmall-6             1.85ns ± 5%     1.79ns ± 1%     -2.97%  (p=0.006 n=7+8)
InterfaceSmall-48            0.49ns ±20%     0.48ns ± 5%       ~     (p=0.740 n=7+9)
New                          28.2ns ±20%     26.6ns ± 3%       ~     (p=0.617 n=9+9)
New-6                        4.69ns ± 4%     4.44ns ± 3%     -5.33%  (p=0.001 n=9+9)
New-48                       1.10ns ± 9%     1.08ns ± 6%       ~     (p=0.285 n=9+8)

name                       old alloc/op   new alloc/op    delta
Call                          0.00B           0.00B            ~     (all equal)
Call-6                        0.00B           0.00B            ~     (all equal)
Call-48                       0.00B           0.00B            ~     (all equal)

name                       old allocs/op  new allocs/op   delta
Call                           0.00            0.00            ~     (all equal)
Call-6                         0.00            0.00            ~     (all equal)
Call-48                        0.00            0.00            ~     (all equal)

name                       old speed      new speed       delta
CallArgCopy/size=128        757MB/s ±11%    649MB/s ± 1%    -14.33%  (p=0.000 n=10+7)
CallArgCopy/size=128-6      901MB/s ± 9%   3781MB/s ± 7%   +319.69%  (p=0.000 n=10+9)
CallArgCopy/size=128-48    1.02GB/s ± 2%  14.63GB/s ± 6%  +1337.98%  (p=0.000 n=8+8)
CallArgCopy/size=256       1.45GB/s ± 9%   1.30GB/s ± 5%    -10.17%  (p=0.000 n=10+9)
CallArgCopy/size=256-6     1.73GB/s ±11%   7.28GB/s ± 7%   +320.76%  (p=0.000 n=10+9)
CallArgCopy/size=256-48    2.00GB/s ± 4%  27.46GB/s ± 9%  +1270.85%  (p=0.000 n=10+9)
CallArgCopy/size=1024      5.21GB/s ± 6%   4.49GB/s ± 8%    -13.74%  (p=0.000 n=10+9)
CallArgCopy/size=1024-6    7.18GB/s ± 7%  24.17GB/s ± 5%   +236.64%  (p=0.000 n=9+8)
CallArgCopy/size=1024-48   7.87GB/s ± 7%  98.43GB/s ± 1%  +1150.99%  (p=0.000 n=10+8)
CallArgCopy/size=4096      12.3GB/s ± 6%   11.7GB/s ± 5%     -5.00%  (p=0.008 n=9+9)
CallArgCopy/size=4096-6    23.8GB/s ±16%   65.6GB/s ± 5%   +175.02%  (p=0.000 n=10+8)
CallArgCopy/size=4096-48   29.0GB/s ± 7%  279.6GB/s ± 6%   +862.87%  (p=0.000 n=10+8)
CallArgCopy/size=65536     8.52GB/s ±11%   8.49GB/s ± 9%       ~     (p=0.842 n=10+9)
CallArgCopy/size=65536-6   49.3GB/s ± 4%   49.0GB/s ± 6%       ~     (p=0.720 n=10+9)
CallArgCopy/size=65536-48   189GB/s ± 2%    190GB/s ± 2%       ~     (p=0.211 n=10+9)

https://perf.golang.org/search?q=upload:20170426.3

Change-Id: Iff68f18ef69defb7f30962e21736ac7685a48a27
Reviewed-on: https://go-review.googlesource.com/41871
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
src/reflect/type.go

index 13d63489f7022ec5ecc2aaf2a4b2f935c63cd5f2..368b27ba518595675a5bde16488985367d8ae7df 100644 (file)
@@ -782,18 +782,12 @@ func (t *rtype) pointers() bool { return t.kind&kindNoPointers == 0 }
 
 func (t *rtype) common() *rtype { return t }
 
-var methodCache struct {
-       sync.RWMutex
-       m map[*rtype][]method
-}
+var methodCache sync.Map // map[*rtype][]method
 
 func (t *rtype) exportedMethods() []method {
-       methodCache.RLock()
-       methods, found := methodCache.m[t]
-       methodCache.RUnlock()
-
+       methodsi, found := methodCache.Load(t)
        if found {
-               return methods
+               return methodsi.([]method)
        }
 
        ut := t.uncommon()
@@ -809,6 +803,7 @@ func (t *rtype) exportedMethods() []method {
                        break
                }
        }
+       var methods []method
        if allExported {
                methods = allm
        } else {
@@ -822,14 +817,8 @@ func (t *rtype) exportedMethods() []method {
                methods = methods[:len(methods):len(methods)]
        }
 
-       methodCache.Lock()
-       if methodCache.m == nil {
-               methodCache.m = make(map[*rtype][]method)
-       }
-       methodCache.m[t] = methods
-       methodCache.Unlock()
-
-       return methods
+       methodsi, _ = methodCache.LoadOrStore(t, methods)
+       return methodsi.([]method)
 }
 
 func (t *rtype) NumMethod() int {
@@ -838,7 +827,7 @@ func (t *rtype) NumMethod() int {
                return tt.NumMethod()
        }
        if t.tflag&tflagUncommon == 0 {
-               return 0 // avoid methodCache lock in zero case
+               return 0 // avoid methodCache synchronization
        }
        return len(t.exportedMethods())
 }
@@ -1410,10 +1399,7 @@ func TypeOf(i interface{}) Type {
 }
 
 // ptrMap is the cache for PtrTo.
-var ptrMap struct {
-       sync.RWMutex
-       m map[*rtype]*ptrType
-}
+var ptrMap sync.Map // map[*rtype]*ptrType
 
 // PtrTo returns the pointer type with element t.
 // For example, if t represents type Foo, PtrTo(t) represents *Foo.
@@ -1427,35 +1413,19 @@ func (t *rtype) ptrTo() *rtype {
        }
 
        // Check the cache.
-       ptrMap.RLock()
-       if m := ptrMap.m; m != nil {
-               if p := m[t]; p != nil {
-                       ptrMap.RUnlock()
-                       return &p.rtype
-               }
-       }
-       ptrMap.RUnlock()
-
-       ptrMap.Lock()
-       if ptrMap.m == nil {
-               ptrMap.m = make(map[*rtype]*ptrType)
-       }
-       p := ptrMap.m[t]
-       if p != nil {
-               // some other goroutine won the race and created it
-               ptrMap.Unlock()
-               return &p.rtype
+       if pi, ok := ptrMap.Load(t); ok {
+               return &pi.(*ptrType).rtype
        }
 
        // Look in known types.
        s := "*" + t.String()
        for _, tt := range typesByString(s) {
-               p = (*ptrType)(unsafe.Pointer(tt))
-               if p.elem == t {
-                       ptrMap.m[t] = p
-                       ptrMap.Unlock()
-                       return &p.rtype
+               p := (*ptrType)(unsafe.Pointer(tt))
+               if p.elem != t {
+                       continue
                }
+               pi, _ := ptrMap.LoadOrStore(t, p)
+               return &pi.(*ptrType).rtype
        }
 
        // Create a new ptrType starting with the description
@@ -1476,9 +1446,8 @@ func (t *rtype) ptrTo() *rtype {
 
        pp.elem = t
 
-       ptrMap.m[t] = &pp
-       ptrMap.Unlock()
-       return &pp.rtype
+       pi, _ := ptrMap.LoadOrStore(t, &pp)
+       return &pi.(*ptrType).rtype
 }
 
 // fnv1 incorporates the list of bytes into the hash x using the FNV-1 hash function.
@@ -1779,10 +1748,7 @@ func typesByString(s string) []*rtype {
 }
 
 // The lookupCache caches ArrayOf, ChanOf, MapOf and SliceOf lookups.
-var lookupCache struct {
-       sync.RWMutex
-       m map[cacheKey]*rtype
-}
+var lookupCache sync.Map // map[cacheKey]*rtype
 
 // A cacheKey is the key for use in the lookupCache.
 // Four values describe any of the types we are looking for:
@@ -1794,47 +1760,15 @@ type cacheKey struct {
        extra uintptr
 }
 
-// cacheGet looks for a type under the key k in the lookupCache.
-// If it finds one, it returns that type.
-// If not, it returns nil with the cache locked.
-// The caller is expected to use cachePut to unlock the cache.
-func cacheGet(k cacheKey) Type {
-       lookupCache.RLock()
-       t := lookupCache.m[k]
-       lookupCache.RUnlock()
-       if t != nil {
-               return t
-       }
-
-       lookupCache.Lock()
-       t = lookupCache.m[k]
-       if t != nil {
-               lookupCache.Unlock()
-               return t
-       }
-
-       if lookupCache.m == nil {
-               lookupCache.m = make(map[cacheKey]*rtype)
-       }
-
-       return nil
-}
-
-// cachePut stores the given type in the cache, unlocks the cache,
-// and returns the type. It is expected that the cache is locked
-// because cacheGet returned nil.
-func cachePut(k cacheKey, t *rtype) Type {
-       lookupCache.m[k] = t
-       lookupCache.Unlock()
-       return t
-}
-
 // The funcLookupCache caches FuncOf lookups.
 // FuncOf does not share the common lookupCache since cacheKey is not
 // sufficient to represent functions unambiguously.
 var funcLookupCache struct {
-       sync.RWMutex
-       m map[uint32][]*rtype // keyed by hash calculated in FuncOf
+       sync.Mutex // Guards stores (but not loads) on m.
+
+       // m is a map[uint32][]*rtype keyed by the hash calculated in FuncOf.
+       // Elements of m are append-only and thus safe for concurrent reading.
+       m sync.Map
 }
 
 // ChanOf returns the channel type with the given direction and element type.
@@ -1847,13 +1781,12 @@ func ChanOf(dir ChanDir, t Type) Type {
 
        // Look in cache.
        ckey := cacheKey{Chan, typ, nil, uintptr(dir)}
-       if ch := cacheGet(ckey); ch != nil {
-               return ch
+       if ch, ok := lookupCache.Load(ckey); ok {
+               return ch.(*rtype)
        }
 
        // This restriction is imposed by the gc compiler and the runtime.
        if typ.size >= 1<<16 {
-               lookupCache.Unlock()
                panic("reflect.ChanOf: element size too large")
        }
 
@@ -1862,7 +1795,6 @@ func ChanOf(dir ChanDir, t Type) Type {
        var s string
        switch dir {
        default:
-               lookupCache.Unlock()
                panic("reflect.ChanOf: invalid dir")
        case SendDir:
                s = "chan<- " + typ.String()
@@ -1874,7 +1806,8 @@ func ChanOf(dir ChanDir, t Type) Type {
        for _, tt := range typesByString(s) {
                ch := (*chanType)(unsafe.Pointer(tt))
                if ch.elem == typ && ch.dir == uintptr(dir) {
-                       return cachePut(ckey, tt)
+                       ti, _ := lookupCache.LoadOrStore(ckey, tt)
+                       return ti.(Type)
                }
        }
 
@@ -1888,7 +1821,8 @@ func ChanOf(dir ChanDir, t Type) Type {
        ch.hash = fnv1(typ.hash, 'c', byte(dir))
        ch.elem = typ
 
-       return cachePut(ckey, &ch.rtype)
+       ti, _ := lookupCache.LoadOrStore(ckey, &ch.rtype)
+       return ti.(Type)
 }
 
 func ismapkey(*rtype) bool // implemented in runtime
@@ -1909,8 +1843,8 @@ func MapOf(key, elem Type) Type {
 
        // Look in cache.
        ckey := cacheKey{Map, ktyp, etyp, 0}
-       if mt := cacheGet(ckey); mt != nil {
-               return mt
+       if mt, ok := lookupCache.Load(ckey); ok {
+               return mt.(Type)
        }
 
        // Look in known types.
@@ -1918,7 +1852,8 @@ func MapOf(key, elem Type) Type {
        for _, tt := range typesByString(s) {
                mt := (*mapType)(unsafe.Pointer(tt))
                if mt.key == ktyp && mt.elem == etyp {
-                       return cachePut(ckey, tt)
+                       ti, _ := lookupCache.LoadOrStore(ckey, tt)
+                       return ti.(Type)
                }
        }
 
@@ -1950,7 +1885,8 @@ func MapOf(key, elem Type) Type {
        mt.needkeyupdate = needKeyUpdate(ktyp)
        mt.ptrToThis = 0
 
-       return cachePut(ckey, &mt.rtype)
+       ti, _ := lookupCache.LoadOrStore(ckey, &mt.rtype)
+       return ti.(Type)
 }
 
 type funcTypeFixed4 struct {
@@ -2055,42 +1991,46 @@ func FuncOf(in, out []Type, variadic bool) Type {
        }
 
        // Look in cache.
-       funcLookupCache.RLock()
-       for _, t := range funcLookupCache.m[hash] {
-               if haveIdenticalUnderlyingType(&ft.rtype, t, true) {
-                       funcLookupCache.RUnlock()
-                       return t
+       if ts, ok := funcLookupCache.m.Load(hash); ok {
+               for _, t := range ts.([]*rtype) {
+                       if haveIdenticalUnderlyingType(&ft.rtype, t, true) {
+                               return t
+                       }
                }
        }
-       funcLookupCache.RUnlock()
 
        // Not in cache, lock and retry.
        funcLookupCache.Lock()
        defer funcLookupCache.Unlock()
-       if funcLookupCache.m == nil {
-               funcLookupCache.m = make(map[uint32][]*rtype)
+       if ts, ok := funcLookupCache.m.Load(hash); ok {
+               for _, t := range ts.([]*rtype) {
+                       if haveIdenticalUnderlyingType(&ft.rtype, t, true) {
+                               return t
+                       }
+               }
        }
-       for _, t := range funcLookupCache.m[hash] {
-               if haveIdenticalUnderlyingType(&ft.rtype, t, true) {
-                       return t
+
+       addToCache := func(tt *rtype) Type {
+               var rts []*rtype
+               if rti, ok := funcLookupCache.m.Load(hash); ok {
+                       rts = rti.([]*rtype)
                }
+               funcLookupCache.m.Store(hash, append(rts, tt))
+               return tt
        }
 
        // Look in known types for the same string representation.
        str := funcStr(ft)
        for _, tt := range typesByString(str) {
                if haveIdenticalUnderlyingType(&ft.rtype, tt, true) {
-                       funcLookupCache.m[hash] = append(funcLookupCache.m[hash], tt)
-                       return tt
+                       return addToCache(tt)
                }
        }
 
        // Populate the remaining fields of ft and store in cache.
        ft.str = resolveReflectName(newName(str, "", "", false))
        ft.ptrToThis = 0
-       funcLookupCache.m[hash] = append(funcLookupCache.m[hash], &ft.rtype)
-
-       return &ft.rtype
+       return addToCache(&ft.rtype)
 }
 
 // funcStr builds a string representation of a funcType.
@@ -2294,8 +2234,8 @@ func SliceOf(t Type) Type {
 
        // Look in cache.
        ckey := cacheKey{Slice, typ, nil, 0}
-       if slice := cacheGet(ckey); slice != nil {
-               return slice
+       if slice, ok := lookupCache.Load(ckey); ok {
+               return slice.(Type)
        }
 
        // Look in known types.
@@ -2303,7 +2243,8 @@ func SliceOf(t Type) Type {
        for _, tt := range typesByString(s) {
                slice := (*sliceType)(unsafe.Pointer(tt))
                if slice.elem == typ {
-                       return cachePut(ckey, tt)
+                       ti, _ := lookupCache.LoadOrStore(ckey, tt)
+                       return ti.(Type)
                }
        }
 
@@ -2317,17 +2258,19 @@ func SliceOf(t Type) Type {
        slice.elem = typ
        slice.ptrToThis = 0
 
-       return cachePut(ckey, &slice.rtype)
+       ti, _ := lookupCache.LoadOrStore(ckey, &slice.rtype)
+       return ti.(Type)
 }
 
 // The structLookupCache caches StructOf lookups.
 // StructOf does not share the common lookupCache since we need to pin
 // the memory associated with *structTypeFixedN.
 var structLookupCache struct {
-       sync.RWMutex
-       m map[uint32][]interface {
-               common() *rtype
-       } // keyed by hash calculated in StructOf
+       sync.Mutex // Guards stores (but not loads) on m.
+
+       // m is a map[uint32][]Type keyed by the hash calculated in StructOf.
+       // Elements in m are append-only and thus safe for concurrent reading.
+       m sync.Map
 }
 
 type structTypeUncommon struct {
@@ -2581,40 +2524,32 @@ func StructOf(fields []StructField) Type {
 
        var typ *structType
        var ut *uncommonType
-       var typPin interface {
-               common() *rtype
-       } // structTypeFixedN
 
        switch {
        case len(methods) == 0:
                t := new(structTypeUncommon)
                typ = &t.structType
                ut = &t.u
-               typPin = t
        case len(methods) <= 4:
                t := new(structTypeFixed4)
                typ = &t.structType
                ut = &t.u
                copy(t.m[:], methods)
-               typPin = t
        case len(methods) <= 8:
                t := new(structTypeFixed8)
                typ = &t.structType
                ut = &t.u
                copy(t.m[:], methods)
-               typPin = t
        case len(methods) <= 16:
                t := new(structTypeFixed16)
                typ = &t.structType
                ut = &t.u
                copy(t.m[:], methods)
-               typPin = t
        case len(methods) <= 32:
                t := new(structTypeFixed32)
                typ = &t.structType
                ut = &t.u
                copy(t.m[:], methods)
-               typPin = t
        default:
                panic("reflect.StructOf: too many methods")
        }
@@ -2637,30 +2572,35 @@ func StructOf(fields []StructField) Type {
        *typ = *prototype
        typ.fields = fs
 
-       // Look in cache
-       structLookupCache.RLock()
-       for _, st := range structLookupCache.m[hash] {
-               t := st.common()
-               if haveIdenticalUnderlyingType(&typ.rtype, t, true) {
-                       structLookupCache.RUnlock()
-                       return t
+       // Look in cache.
+       if ts, ok := structLookupCache.m.Load(hash); ok {
+               for _, st := range ts.([]Type) {
+                       t := st.common()
+                       if haveIdenticalUnderlyingType(&typ.rtype, t, true) {
+                               return t
+                       }
                }
        }
-       structLookupCache.RUnlock()
 
-       // not in cache, lock and retry
+       // Not in cache, lock and retry.
        structLookupCache.Lock()
        defer structLookupCache.Unlock()
-       if structLookupCache.m == nil {
-               structLookupCache.m = make(map[uint32][]interface {
-                       common() *rtype
-               })
+       if ts, ok := structLookupCache.m.Load(hash); ok {
+               for _, st := range ts.([]Type) {
+                       t := st.common()
+                       if haveIdenticalUnderlyingType(&typ.rtype, t, true) {
+                               return t
+                       }
+               }
        }
-       for _, st := range structLookupCache.m[hash] {
-               t := st.common()
-               if haveIdenticalUnderlyingType(&typ.rtype, t, true) {
-                       return t
+
+       addToCache := func(t Type) Type {
+               var ts []Type
+               if ti, ok := structLookupCache.m.Load(hash); ok {
+                       ts = ti.([]Type)
                }
+               structLookupCache.m.Store(hash, append(ts, t))
+               return t
        }
 
        // Look in known types.
@@ -2669,8 +2609,7 @@ func StructOf(fields []StructField) Type {
                        // even if 't' wasn't a structType with methods, we should be ok
                        // as the 'u uncommonType' field won't be accessed except when
                        // tflag&tflagUncommon is set.
-                       structLookupCache.m[hash] = append(structLookupCache.m[hash], t)
-                       return t
+                       return addToCache(t)
                }
        }
 
@@ -2781,8 +2720,7 @@ func StructOf(fields []StructField) Type {
                typ.kind &^= kindDirectIface
        }
 
-       structLookupCache.m[hash] = append(structLookupCache.m[hash], typPin)
-       return &typ.rtype
+       return addToCache(&typ.rtype)
 }
 
 func runtimeStructField(field StructField) structField {
@@ -2846,15 +2784,11 @@ const maxPtrmaskBytes = 2048
 // ArrayOf panics.
 func ArrayOf(count int, elem Type) Type {
        typ := elem.(*rtype)
-       // call SliceOf here as it calls cacheGet/cachePut.
-       // ArrayOf also calls cacheGet/cachePut and thus may modify the state of
-       // the lookupCache mutex.
-       slice := SliceOf(elem)
 
        // Look in cache.
        ckey := cacheKey{Array, typ, nil, uintptr(count)}
-       if array := cacheGet(ckey); array != nil {
-               return array
+       if array, ok := lookupCache.Load(ckey); ok {
+               return array.(Type)
        }
 
        // Look in known types.
@@ -2862,7 +2796,8 @@ func ArrayOf(count int, elem Type) Type {
        for _, tt := range typesByString(s) {
                array := (*arrayType)(unsafe.Pointer(tt))
                if array.elem == typ {
-                       return cachePut(ckey, tt)
+                       ti, _ := lookupCache.LoadOrStore(ckey, tt)
+                       return ti.(Type)
                }
        }
 
@@ -2889,7 +2824,7 @@ func ArrayOf(count int, elem Type) Type {
        array.align = typ.align
        array.fieldAlign = typ.fieldAlign
        array.len = uintptr(count)
-       array.slice = slice.(*rtype)
+       array.slice = SliceOf(elem).(*rtype)
 
        array.kind &^= kindNoPointers
        switch {
@@ -3008,7 +2943,8 @@ func ArrayOf(count int, elem Type) Type {
                array.kind &^= kindDirectIface
        }
 
-       return cachePut(ckey, &array.rtype)
+       ti, _ := lookupCache.LoadOrStore(ckey, &array.rtype)
+       return ti.(Type)
 }
 
 func appendVarint(x []byte, v uintptr) []byte {
@@ -3044,10 +2980,7 @@ type layoutType struct {
        framePool *sync.Pool
 }
 
-var layoutCache struct {
-       sync.RWMutex
-       m map[layoutKey]layoutType
-}
+var layoutCache sync.Map // map[layoutKey]layoutType
 
 // funcLayout computes a struct type representing the layout of the
 // function arguments and return values for the function type t.
@@ -3063,16 +2996,9 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
                panic("reflect: funcLayout with interface receiver " + rcvr.String())
        }
        k := layoutKey{t, rcvr}
-       layoutCache.RLock()
-       if x := layoutCache.m[k]; x.t != nil {
-               layoutCache.RUnlock()
-               return x.t, x.argSize, x.retOffset, x.stack, x.framePool
-       }
-       layoutCache.RUnlock()
-       layoutCache.Lock()
-       if x := layoutCache.m[k]; x.t != nil {
-               layoutCache.Unlock()
-               return x.t, x.argSize, x.retOffset, x.stack, x.framePool
+       if lti, ok := layoutCache.Load(k); ok {
+               lt := lti.(layoutType)
+               return lt.t, lt.argSize, lt.retOffset, lt.stack, lt.framePool
        }
 
        tt := (*funcType)(unsafe.Pointer(t))
@@ -3133,21 +3059,18 @@ func funcLayout(t *rtype, rcvr *rtype) (frametype *rtype, argSize, retOffset uin
        x.str = resolveReflectName(newName(s, "", "", false))
 
        // cache result for future callers
-       if layoutCache.m == nil {
-               layoutCache.m = make(map[layoutKey]layoutType)
-       }
        framePool = &sync.Pool{New: func() interface{} {
                return unsafe_New(x)
        }}
-       layoutCache.m[k] = layoutType{
+       lti, _ := layoutCache.LoadOrStore(k, layoutType{
                t:         x,
                argSize:   argSize,
                retOffset: retOffset,
                stack:     ptrmap,
                framePool: framePool,
-       }
-       layoutCache.Unlock()
-       return x, argSize, retOffset, ptrmap, framePool
+       })
+       lt := lti.(layoutType)
+       return lt.t, lt.argSize, lt.retOffset, lt.stack, lt.framePool
 }
 
 // ifaceIndir reports whether t is stored indirectly in an interface value.