From e2da85ba9d0ff84eb1e810d02643b67361b73d6b Mon Sep 17 00:00:00 2001 From: "Sangyeop.lee" Date: Tue, 1 Jun 2021 11:22:54 +0900 Subject: [PATCH 1/5] perf: memoize bech32 encoding and decoding --- types/address.go | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/types/address.go b/types/address.go index 6b1a11c32f..6a4fdb6942 100644 --- a/types/address.go +++ b/types/address.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/dgraph-io/ristretto" yaml "gopkg.in/yaml.v2" "github.com/line/lbm-sdk/v2/codec/legacy" @@ -92,6 +93,27 @@ var _ yaml.Marshaler = ConsAddress{} // account // ---------------------------------------------------------------------------- +// bech32 encoding and decoding takes a lot of time, so memoize it +var bech32ToAddrCache *ristretto.Cache +var addrToBech32Cache *ristretto.Cache + +func init() { + var err error + config := &ristretto.Config{ + NumCounters: 1e7, // number of keys to track frequency of (10M). + MaxCost: 1 << 30, // maximum cost of cache (1GB). + BufferItems: 64, // number of keys per Get buffer. + } + bech32ToAddrCache, err = ristretto.NewCache(config) + if err != nil { + panic(err) + } + addrToBech32Cache, err = ristretto.NewCache(config) + if err != nil { + panic(err) + } +} + // AccAddress a wrapper around bytes meant to represent an account address. // When marshaled to a string or JSON, it uses Bech32. type AccAddress []byte @@ -117,7 +139,12 @@ func VerifyAddressFormat(bz []byte) error { } // AccAddressFromBech32 creates an AccAddress from a Bech32 string. -func AccAddressFromBech32(address string) (addr AccAddress, err error) { +func AccAddressFromBech32(address string) (AccAddress, error) { + addr, ok := bech32ToAddrCache.Get(address) + if ok { + return addr.([]byte), nil + } + if len(strings.TrimSpace(address)) == 0 { return AccAddress{}, errors.New("empty address string is not allowed") } @@ -133,8 +160,9 @@ func AccAddressFromBech32(address string) (addr AccAddress, err error) { if err != nil { return nil, err } + bech32ToAddrCache.Set(address, bz, 1) - return AccAddress(bz), nil + return bz, nil } // Returns boolean for whether two AccAddresses are Equal @@ -229,6 +257,11 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { + addr, ok := addrToBech32Cache.Get(string(aa)) + if ok { + return addr.(string) + } + if aa.Empty() { return "" } @@ -239,6 +272,7 @@ func (aa AccAddress) String() string { if err != nil { panic(err) } + addrToBech32Cache.Set(string(aa), bech32Addr, 1) return bech32Addr } From 8931085944eed15bec37ac3a94bf5152e822c0d8 Mon Sep 17 00:00:00 2001 From: "Sangyeop.lee" Date: Tue, 1 Jun 2021 11:23:25 +0900 Subject: [PATCH 2/5] build: add ristretto to go.mod --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 6846982002..17753c9984 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/confio/ics23/go v0.6.3 github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ledger-cosmos-go v0.11.1 + github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b github.com/enigmampc/btcutil v1.0.3-0.20200723161021-e2fb6adb2a25 github.com/go-kit/kit v0.10.0 From 266ecdd70191fea8824ab5cf978280a9d309b3bf Mon Sep 17 00:00:00 2001 From: "Sangyeop.lee" Date: Tue, 1 Jun 2021 14:15:30 +0900 Subject: [PATCH 3/5] chore: add bech32 cache size to configs --- server/config/config.go | 4 ++++ server/config/toml.go | 3 +++ server/start.go | 1 + simapp/simd/cmd/root.go | 5 +++++ types/address.go | 38 ++++++++++++++++++++++++-------------- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/server/config/config.go b/server/config/config.go index 94472ef424..8e9cb68cf4 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -74,6 +74,9 @@ type BaseConfig struct { // IAVL cache size; bytes size unit IAVLCacheSize int `mapstructure:"iavl-cache-size"` + // Bech32CacheSize is the maximum bytes size of bech32 cache (Default : 1GB) + Bech32CacheSize int `mapstructure:"bech32-cache-size"` + // When true, Prometheus metrics are served under /metrics on prometheus_listen_addr in config.toml. // It works when tendermint's prometheus option (config.toml) is set to true. Prometheus bool `mapstructure:"prometheus"` @@ -179,6 +182,7 @@ func DefaultConfig() *Config { InterBlockCache: true, InterBlockCacheSize: cache.DefaultCommitKVStoreCacheSize, IAVLCacheSize: iavl.DefaultIAVLCacheSize, + Bech32CacheSize: sdk.DefaultBech32CacheSize, Pruning: storetypes.PruningOptionDefault, PruningKeepRecent: "0", PruningKeepEvery: "0", diff --git a/server/config/toml.go b/server/config/toml.go index d1fa97e0a7..de057ebdfa 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -69,6 +69,9 @@ inter-block-cache-size = {{ .BaseConfig.InterBlockCacheSize }} # IAVLCacheSize is the maximum bytes size of iavl node cache iavl-cache-size = {{ .BaseConfig.IAVLCacheSize }} +# Bech32CacheSize is the maximum bytes size of bech32 cache (Default : 1GB) +bech32-cache-size = {{ .BaseConfig.Bech32CacheSize }} + # IndexEvents defines the set of events in the form {eventType}.{attributeKey}, # which informs Tendermint what to index. If empty, all events will be indexed. # diff --git a/server/start.go b/server/start.go index 45826b313d..af59d72f96 100644 --- a/server/start.go +++ b/server/start.go @@ -43,6 +43,7 @@ const ( FlagInterBlockCache = "inter-block-cache" FlagInterBlockCacheSize = "inter-block-cache-size" FlagIAVLCacheSize = "iavl-cache-size" + FlagBech32CacheSize = "bech32-cache-size" FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades" FlagTrace = "trace" FlagInvCheckPeriod = "inv-check-period" diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go index b763b9a2d1..3de911106c 100644 --- a/simapp/simd/cmd/root.go +++ b/simapp/simd/cmd/root.go @@ -165,6 +165,11 @@ func (a appCreator) newApp(logger log.Logger, db tmdb.DB, traceStore io.Writer, cast.ToInt(appOpts.Get(server.FlagInterBlockCacheSize)), ibCacheMetricsProvider) } + bech32CacheSize := cast.ToInt(appOpts.Get(server.FlagBech32CacheSize)) + if bech32CacheSize > 0 { + sdk.SetBech32Cache(int64(bech32CacheSize)) + } + skipUpgradeHeights := make(map[int64]bool) for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { skipUpgradeHeights[int64(h)] = true diff --git a/types/address.go b/types/address.go index 6a4fdb6942..7db69581ab 100644 --- a/types/address.go +++ b/types/address.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "strings" "github.com/dgraph-io/ristretto" @@ -97,12 +98,15 @@ var _ yaml.Marshaler = ConsAddress{} var bech32ToAddrCache *ristretto.Cache var addrToBech32Cache *ristretto.Cache -func init() { +const DefaultBech32CacheSize = 1 << 30 // maximum size of cache (1GB). + +func SetBech32Cache(size int64) { + log.Println(size) var err error config := &ristretto.Config{ - NumCounters: 1e7, // number of keys to track frequency of (10M). - MaxCost: 1 << 30, // maximum cost of cache (1GB). - BufferItems: 64, // number of keys per Get buffer. + NumCounters: 1e7, // number of keys to track frequency of (10M). + MaxCost: size, + BufferItems: 64, // number of keys per Get buffer. } bech32ToAddrCache, err = ristretto.NewCache(config) if err != nil { @@ -140,9 +144,11 @@ func VerifyAddressFormat(bz []byte) error { // AccAddressFromBech32 creates an AccAddress from a Bech32 string. func AccAddressFromBech32(address string) (AccAddress, error) { - addr, ok := bech32ToAddrCache.Get(address) - if ok { - return addr.([]byte), nil + if bech32ToAddrCache != nil { + addr, ok := bech32ToAddrCache.Get(address) + if ok { + return addr.([]byte), nil + } } if len(strings.TrimSpace(address)) == 0 { @@ -160,8 +166,9 @@ func AccAddressFromBech32(address string) (AccAddress, error) { if err != nil { return nil, err } - bech32ToAddrCache.Set(address, bz, 1) - + if bech32ToAddrCache != nil { + bech32ToAddrCache.Set(address, bz, int64(len(bz))) + } return bz, nil } @@ -257,9 +264,11 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { - addr, ok := addrToBech32Cache.Get(string(aa)) - if ok { - return addr.(string) + if addrToBech32Cache != nil { + addr, ok := addrToBech32Cache.Get(string(aa)) + if ok { + return addr.(string) + } } if aa.Empty() { @@ -272,8 +281,9 @@ func (aa AccAddress) String() string { if err != nil { panic(err) } - addrToBech32Cache.Set(string(aa), bech32Addr, 1) - + if addrToBech32Cache != nil { + addrToBech32Cache.Set(string(aa), bech32Addr, int64(len(bech32Addr))) + } return bech32Addr } From e7457af4c8130ca65a61743a1de9a07e2e072002 Mon Sep 17 00:00:00 2001 From: "Sangyeop.lee" Date: Tue, 1 Jun 2021 14:54:16 +0900 Subject: [PATCH 4/5] docs: add a todo comment --- types/address.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/address.go b/types/address.go index 7db69581ab..4b5a3e82e8 100644 --- a/types/address.go +++ b/types/address.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "strings" "github.com/dgraph-io/ristretto" @@ -94,6 +93,7 @@ var _ yaml.Marshaler = ConsAddress{} // account // ---------------------------------------------------------------------------- +// TODO We need to create a type that wraps the two caches, and add a layer that references the cache through that type. // bech32 encoding and decoding takes a lot of time, so memoize it var bech32ToAddrCache *ristretto.Cache var addrToBech32Cache *ristretto.Cache @@ -101,7 +101,6 @@ var addrToBech32Cache *ristretto.Cache const DefaultBech32CacheSize = 1 << 30 // maximum size of cache (1GB). func SetBech32Cache(size int64) { - log.Println(size) var err error config := &ristretto.Config{ NumCounters: 1e7, // number of keys to track frequency of (10M). From 870d1b752e62e568ab61033b0436c2d170ce9394 Mon Sep 17 00:00:00 2001 From: "Sangyeop.lee" Date: Tue, 1 Jun 2021 16:14:12 +0900 Subject: [PATCH 5/5] chore: add Bech32Cache to wrap two caches --- types/address.go | 71 +++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/types/address.go b/types/address.go index 4b5a3e82e8..35a8982465 100644 --- a/types/address.go +++ b/types/address.go @@ -93,12 +93,14 @@ var _ yaml.Marshaler = ConsAddress{} // account // ---------------------------------------------------------------------------- -// TODO We need to create a type that wraps the two caches, and add a layer that references the cache through that type. +// TODO We should add a layer to choose whether to access the cache or to run actual conversion // bech32 encoding and decoding takes a lot of time, so memoize it -var bech32ToAddrCache *ristretto.Cache -var addrToBech32Cache *ristretto.Cache +var bech32Cache Bech32Cache -const DefaultBech32CacheSize = 1 << 30 // maximum size of cache (1GB). +type Bech32Cache struct { + bech32ToAddrCache *ristretto.Cache + addrToBech32Cache *ristretto.Cache +} func SetBech32Cache(size int64) { var err error @@ -107,16 +109,43 @@ func SetBech32Cache(size int64) { MaxCost: size, BufferItems: 64, // number of keys per Get buffer. } - bech32ToAddrCache, err = ristretto.NewCache(config) + bech32Cache.bech32ToAddrCache, err = ristretto.NewCache(config) if err != nil { panic(err) } - addrToBech32Cache, err = ristretto.NewCache(config) + bech32Cache.addrToBech32Cache, err = ristretto.NewCache(config) if err != nil { panic(err) } } +func (cache *Bech32Cache) GetAddr(bech32Addr string) ([]byte, bool) { + if cache.bech32ToAddrCache != nil { + rawAddr, ok := cache.bech32ToAddrCache.Get(bech32Addr) + return rawAddr.([]byte), ok + } + return nil, false +} + +func (cache *Bech32Cache) GetBech32(rawAddr []byte) (string, bool) { + if cache.bech32ToAddrCache != nil { + bech32Addr, ok := cache.bech32ToAddrCache.Get(rawAddr) + return bech32Addr.(string), ok + } + return "", false +} + +func (cache *Bech32Cache) Set(bech32Addr string, rawAddr []byte) { + if cache.bech32ToAddrCache != nil { + cache.bech32ToAddrCache.Set(bech32Addr, rawAddr, int64(len(rawAddr))) + } + if cache.addrToBech32Cache != nil { + cache.addrToBech32Cache.Set(string(rawAddr), bech32Addr, int64(len(bech32Addr))) + } +} + +const DefaultBech32CacheSize = 1 << 30 // maximum size of cache (1GB). + // AccAddress a wrapper around bytes meant to represent an account address. // When marshaled to a string or JSON, it uses Bech32. type AccAddress []byte @@ -142,21 +171,19 @@ func VerifyAddressFormat(bz []byte) error { } // AccAddressFromBech32 creates an AccAddress from a Bech32 string. -func AccAddressFromBech32(address string) (AccAddress, error) { - if bech32ToAddrCache != nil { - addr, ok := bech32ToAddrCache.Get(address) - if ok { - return addr.([]byte), nil - } +func AccAddressFromBech32(bech32Addr string) (AccAddress, error) { + addr, ok := bech32Cache.GetAddr(bech32Addr) + if ok { + return addr, nil } - if len(strings.TrimSpace(address)) == 0 { + if len(strings.TrimSpace(bech32Addr)) == 0 { return AccAddress{}, errors.New("empty address string is not allowed") } bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() - bz, err := GetFromBech32(address, bech32PrefixAccAddr) + bz, err := GetFromBech32(bech32Addr, bech32PrefixAccAddr) if err != nil { return nil, err } @@ -165,9 +192,7 @@ func AccAddressFromBech32(address string) (AccAddress, error) { if err != nil { return nil, err } - if bech32ToAddrCache != nil { - bech32ToAddrCache.Set(address, bz, int64(len(bz))) - } + bech32Cache.Set(bech32Addr, bz) return bz, nil } @@ -263,11 +288,9 @@ func (aa AccAddress) Bytes() []byte { // String implements the Stringer interface. func (aa AccAddress) String() string { - if addrToBech32Cache != nil { - addr, ok := addrToBech32Cache.Get(string(aa)) - if ok { - return addr.(string) - } + bech32Addr, ok := bech32Cache.GetBech32(aa) + if ok { + return bech32Addr } if aa.Empty() { @@ -280,9 +303,7 @@ func (aa AccAddress) String() string { if err != nil { panic(err) } - if addrToBech32Cache != nil { - addrToBech32Cache.Set(string(aa), bech32Addr, int64(len(bech32Addr))) - } + bech32Cache.Set(bech32Addr, aa) return bech32Addr }