From a97f560a95daf0d2f2166a1cfdcbdb50b5abeb42 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Tue, 17 Feb 2026 14:38:42 +0100 Subject: [PATCH] perf: replace AccAddress.String() mutex+LRU with sync.Map AccAddress.String() uses a global sync.Mutex to protect a simplelru.LRU cache. Under OCC with many goroutines, this serializes all bech32 lookups even on cache hits (LRU.Get modifies ordering, requiring a write lock). The mutex profile shows 26.79s (6.66%) of contention on this path, primarily from bank keeper event emission (AddCoins, Transfer, BuyGas). Replace with sync.Map which provides lock-free reads for cache hits. The tradeoff is losing LRU eviction, but practical address cardinality is bounded by on-chain accounts (similar order as the old 60k cap). Co-Authored-By: Claude Opus 4.6 --- sei-cosmos/types/address.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/sei-cosmos/types/address.go b/sei-cosmos/types/address.go index 2761eb68d8..43b031af31 100644 --- a/sei-cosmos/types/address.go +++ b/sei-cosmos/types/address.go @@ -77,8 +77,10 @@ const ( var ( // AccAddress.String() is expensive and if unoptimized dominantly showed up in profiles, // yet has no mechanisms to trivially cache the result given that AccAddress is a []byte type. - accAddrMu sync.Mutex - accAddrCache *simplelru.LRU[string, string] + // accAddrMap uses sync.Map for lock-free reads; the key is string(AccAddress) and the + // value is the bech32-encoded string. Unlike the LRU it has no eviction, but practical + // address cardinality is bounded by on-chain accounts (similar order as the old 60k cap). + accAddrMap sync.Map consAddrMu sync.Mutex consAddrCache *simplelru.LRU[string, string] valAddrMu sync.Mutex @@ -87,11 +89,7 @@ var ( func init() { var err error - // in total the cache size is 61k entries. Key is 32 bytes and value is around 50-70 bytes. - // That will make around 92 * 61k * 2 (LRU) bytes ~ 11 MB - if accAddrCache, err = simplelru.NewLRU[string, string](60000, nil); err != nil { - panic(err) - } + // consAddrCache and valAddrCache use LRU caches (low cardinality, minimal contention). if consAddrCache, err = simplelru.NewLRU[string, string](500, nil); err != nil { panic(err) } @@ -276,14 +274,18 @@ func (aa AccAddress) String() string { return "" } - var key = conv.UnsafeBytesToStr(aa) - accAddrMu.Lock() - defer accAddrMu.Unlock() - addr, ok := accAddrCache.Get(key) - if ok { - return addr + // Fast path: lock-free lookup (zero allocations for cache hits). + if addr, ok := accAddrMap.Load(conv.UnsafeBytesToStr(aa)); ok { + return addr.(string) } - return cacheBech32Addr(GetConfig().GetBech32AccountAddrPrefix(), aa, accAddrCache, key) + + // Slow path: compute bech32 and store with a stable key copy. + bech32Addr, err := bech32.ConvertAndEncode(GetConfig().GetBech32AccountAddrPrefix(), aa) + if err != nil { + panic(err) + } + accAddrMap.Store(string(aa), bech32Addr) + return bech32Addr } // Format implements the fmt.Formatter interface.