common/hreflect: Replace the map/RWMutex method cache with sync.Map

It's much faster when running in parallel:

```
GetMethodByName-10        125.1n ± 6%   181.7n ± 7%  +45.30% (p=0.002 n=6)
GetMethodByNamePara-10   770.10n ± 1%   24.77n ± 9%  -96.78% (p=0.002 n=6)
```
This commit is contained in:
Bjørn Erik Pedersen 2025-03-26 11:11:06 +01:00
parent 26d986fc0d
commit 8d2379bcb3
2 changed files with 19 additions and 16 deletions

View file

@ -134,12 +134,7 @@ type methodKey struct {
name string name string
} }
type methods struct { var methodCache sync.Map
sync.RWMutex
cache map[methodKey]int
}
var methodCache = &methods{cache: make(map[methodKey]int)}
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the // GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
// type lookup. // type lookup.
@ -157,22 +152,16 @@ func GetMethodByName(v reflect.Value, name string) reflect.Value {
// -1 if no such method exists. // -1 if no such method exists.
func GetMethodIndexByName(tp reflect.Type, name string) int { func GetMethodIndexByName(tp reflect.Type, name string) int {
k := methodKey{tp, name} k := methodKey{tp, name}
methodCache.RLock() v, found := methodCache.Load(k)
index, found := methodCache.cache[k]
methodCache.RUnlock()
if found { if found {
return index return v.(int)
} }
methodCache.Lock()
defer methodCache.Unlock()
m, ok := tp.MethodByName(name) m, ok := tp.MethodByName(name)
index = m.Index index := m.Index
if !ok { if !ok {
index = -1 index = -1
} }
methodCache.cache[k] = index methodCache.Store(k, index)
if !ok { if !ok {
return -1 return -1

View file

@ -134,3 +134,17 @@ func BenchmarkGetMethodByName(b *testing.B) {
} }
} }
} }
func BenchmarkGetMethodByNamePara(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for _, method := range methods {
_ = GetMethodByName(v, method)
}
}
})
}