From 8d2379bcb3ca4c5fdf6b9322bbe05e42b7a81bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 26 Mar 2025 11:11:06 +0100 Subject: [PATCH] common/hreflect: Replace the map/RWMutex method cache with sync.Map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) ``` --- common/hreflect/helpers.go | 21 +++++---------------- common/hreflect/helpers_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go index ab7883a47..545371374 100644 --- a/common/hreflect/helpers.go +++ b/common/hreflect/helpers.go @@ -134,12 +134,7 @@ type methodKey struct { name string } -type methods struct { - sync.RWMutex - cache map[methodKey]int -} - -var methodCache = &methods{cache: make(map[methodKey]int)} +var methodCache sync.Map // GetMethodByName is the same as reflect.Value.MethodByName, but it caches the // type lookup. @@ -157,22 +152,16 @@ func GetMethodByName(v reflect.Value, name string) reflect.Value { // -1 if no such method exists. func GetMethodIndexByName(tp reflect.Type, name string) int { k := methodKey{tp, name} - methodCache.RLock() - index, found := methodCache.cache[k] - methodCache.RUnlock() + v, found := methodCache.Load(k) if found { - return index + return v.(int) } - - methodCache.Lock() - defer methodCache.Unlock() - m, ok := tp.MethodByName(name) - index = m.Index + index := m.Index if !ok { index = -1 } - methodCache.cache[k] = index + methodCache.Store(k, index) if !ok { return -1 diff --git a/common/hreflect/helpers_test.go b/common/hreflect/helpers_test.go index 119722261..cbcad0f22 100644 --- a/common/hreflect/helpers_test.go +++ b/common/hreflect/helpers_test.go @@ -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) + } + } + }) +}