currency: boost retrieval speed when calling currency.NewCode() (#1014)

* currency: optimization pass

* currency: reimplement and fix tests and run benchmark comparison

* Update currency/code_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* glorious: nits

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
Ryan O'Hara-Reid
2022-08-31 10:45:45 +10:00
committed by GitHub
parent 42291fbbfe
commit 7b958d2c05
4 changed files with 225 additions and 135 deletions

View File

@@ -27,6 +27,8 @@ var (
errRoleUnset = errors.New("role unset")
)
// String implements the stringer interface and returns a string representation
// of the underlying role.
func (r Role) String() string {
switch r {
case Fiat:
@@ -89,22 +91,24 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) {
var file File
b.mtx.Lock()
defer b.mtx.Unlock()
for i := range b.Items {
switch b.Items[i].Role {
case Unset:
file.UnsetCurrency = append(file.UnsetCurrency, b.Items[i])
case Fiat:
file.FiatCurrency = append(file.FiatCurrency, b.Items[i])
case Cryptocurrency:
file.Cryptocurrency = append(file.Cryptocurrency, b.Items[i])
case Token:
file.Token = append(file.Token, b.Items[i])
case Contract:
file.Contracts = append(file.Contracts, b.Items[i])
case Stable:
file.Stable = append(file.Stable, b.Items[i])
default:
return file, errors.New("role undefined")
for _, stored := range b.Items {
for x := range stored {
switch stored[x].Role {
case Unset:
file.UnsetCurrency = append(file.UnsetCurrency, stored[x])
case Fiat:
file.FiatCurrency = append(file.FiatCurrency, stored[x])
case Cryptocurrency:
file.Cryptocurrency = append(file.Cryptocurrency, stored[x])
case Token:
file.Token = append(file.Token, stored[x])
case Contract:
file.Contracts = append(file.Contracts, stored[x])
case Stable:
file.Stable = append(file.Stable, stored[x])
default:
return file, errors.New("role undefined")
}
}
}
file.LastMainUpdate = b.LastMainUpdate.Unix()
@@ -116,47 +120,60 @@ func (b *BaseCodes) GetFullCurrencyData() (File, error) {
func (b *BaseCodes) GetCurrencies() Currencies {
b.mtx.Lock()
currencies := make(Currencies, len(b.Items))
for i := range b.Items {
currencies[i] = Code{Item: b.Items[i]}
var target int
for _, stored := range b.Items {
if len(stored) == 0 {
continue
}
currencies[target] = Code{Item: stored[0]}
target++
}
b.mtx.Unlock()
return currencies
}
// UpdateCurrency updates or registers a currency/contract
func (b *BaseCodes) UpdateCurrency(fullName, symbol, blockchain string, id int, r Role) error {
if r == Unset {
return fmt.Errorf("cannot update currency %w for %s", errRoleUnset, symbol)
func (b *BaseCodes) UpdateCurrency(update *Item) error {
if update == nil {
return errItemIsNil
}
if update.Symbol == "" {
return errSymbolEmpty
}
if update.Role == Unset {
return fmt.Errorf("cannot update currency %w for %s", errRoleUnset, update.Symbol)
}
update.Symbol = strings.ToUpper(update.Symbol)
update.Lower = strings.ToLower(update.Symbol)
b.mtx.Lock()
defer b.mtx.Unlock()
for i := range b.Items {
if b.Items[i].Symbol != symbol || (b.Items[i].Role != Unset && b.Items[i].Role != r) {
continue
}
if fullName != "" {
b.Items[i].FullName = fullName
stored, ok := b.Items[update.Symbol]
if ok {
for x := range stored {
if stored[x].Role != Unset && stored[x].Role != update.Role {
continue
}
stored[x].Role = update.Role // NOTE: Update role is checked above.
if update.FullName != "" {
stored[x].FullName = update.FullName
}
if update.AssocChain != "" {
stored[x].AssocChain = update.AssocChain
}
if update.ID != 0 {
stored[x].ID = update.ID
}
return nil
}
if r != Unset {
b.Items[i].Role = r
}
if blockchain != "" {
b.Items[i].AssocChain = blockchain
}
if id != 0 {
b.Items[i].ID = id
}
return nil
}
b.Items = append(b.Items, &Item{
Symbol: symbol,
FullName: fullName,
Role: r,
AssocChain: blockchain,
ID: id,
})
b.Items[update.Symbol] = append(b.Items[update.Symbol], update)
return nil
}
@@ -181,26 +198,30 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
b.mtx.Lock()
defer b.mtx.Unlock()
for i := range b.Items {
if b.Items[i].Symbol != c {
continue
}
if newRole != Unset {
if b.Items[i].Role == Unset {
b.Items[i].Role = newRole
} else if b.Items[i].Role != newRole {
// This will duplicate item with same name but different role.
// TODO: This will need a specific update to NewCode to add in
// a specific param to find the exact name and role.
continue
}
}
return Code{Item: b.Items[i], UpperCase: format}
if b.Items == nil {
b.Items = make(map[string][]*Item)
}
stored, ok := b.Items[c]
if ok {
for x := range stored {
if newRole != Unset {
if stored[x].Role != Unset && stored[x].Role != newRole {
// This will duplicate item with same name but different
// role if not matched in stored list.
// TODO: This will need a specific update to NewCode() or
// new function to find the exact name and role.
continue
}
stored[x].Role = newRole
}
return Code{Item: stored[x], UpperCase: format}
}
}
newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole}
b.Items = append(b.Items, newItem)
b.Items[c] = append(b.Items[c], newItem)
return Code{Item: newItem, UpperCase: format}
}
@@ -219,14 +240,21 @@ func (b *BaseCodes) LoadItem(item *Item) error {
b.mtx.Lock()
defer b.mtx.Unlock()
for i := range b.Items {
if b.Items[i].Symbol != item.Symbol ||
(b.Items[i].Role != Unset && item.Role != Unset && b.Items[i].Role != item.Role) {
continue
stored, ok := b.Items[item.Symbol]
if ok {
for x := range stored {
if stored[x].Role == item.Role {
return nil
}
if stored[x].Role == Unset && item.Role != Unset {
stored[x].Role = item.Role
return nil
}
}
return nil
}
b.Items = append(b.Items, item)
b.Items[item.Symbol] = append(b.Items[item.Symbol], item)
return nil
}

View File

@@ -152,12 +152,16 @@ func (b *BaseCodes) assertRole(t *testing.T, c Code, r Role) {
t.Helper()
b.mtx.Lock()
defer b.mtx.Unlock()
for x := range b.Items {
if b.Items[x] != c.Item {
stored, ok := b.Items[c.Item.Symbol]
if !ok {
t.Fatal("code pointer not found")
}
for x := range stored {
if stored[x] != c.Item {
continue
}
if b.Items[x].Role != r {
t.Fatal("unexpected role")
if stored[x].Role != r {
t.Fatalf("unexpected role received: %v but expected: %v", stored[x].Role, r)
}
return
}
@@ -231,29 +235,42 @@ func TestBaseCode(t *testing.T) {
}
main.Register("XBTUSD", Unset)
err := main.UpdateCurrency("Bitcoin Perpetual",
"XBTUSD",
"",
0,
Contract)
err := main.UpdateCurrency(&Item{
FullName: "Bitcoin Perpetual",
Symbol: "XBTUSD",
Role: Contract,
})
if err != nil {
t.Fatal(err)
}
main.Register("BTC", Unset)
err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Unset)
err = main.UpdateCurrency(&Item{
FullName: "Bitcoin",
Symbol: "BTC",
ID: 1337,
})
if !errors.Is(err, errRoleUnset) {
t.Fatalf("received: '%v' but expected: '%v'", err, errRoleUnset)
}
err = main.UpdateCurrency("Bitcoin", "BTC", "", 1337, Cryptocurrency)
err = main.UpdateCurrency(&Item{
FullName: "Bitcoin",
Symbol: "BTC",
ID: 1337,
Role: Cryptocurrency,
})
if err != nil {
t.Fatal(err)
}
aud := main.Register("AUD", Unset)
err = main.UpdateCurrency("Unreal Dollar", "AUD", "", 1111, Fiat)
err = main.UpdateCurrency(&Item{
FullName: "Unreal Dollar",
Symbol: "AUD",
ID: 1111,
Role: Fiat,
})
if err != nil {
t.Fatal(err)
}
@@ -262,13 +279,23 @@ func TestBaseCode(t *testing.T) {
t.Error("Expected fullname to update for AUD")
}
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
err = main.UpdateCurrency(&Item{
FullName: "Australian Dollar",
Symbol: "AUD",
ID: 1336,
Role: Fiat,
})
if err != nil {
t.Fatal(err)
}
aud.Item.Role = Unset
err = main.UpdateCurrency("Australian Dollar", "AUD", "", 1336, Fiat)
err = main.UpdateCurrency(&Item{
FullName: "Australian Dollar",
Symbol: "AUD",
ID: 1336,
Role: Fiat,
})
if err != nil {
t.Fatal(err)
}
@@ -277,7 +304,13 @@ func TestBaseCode(t *testing.T) {
}
main.Register("PPT", Unset)
err = main.UpdateCurrency("Populous", "PPT", "ETH", 1335, Token)
err = main.UpdateCurrency(&Item{
FullName: "Populous",
Symbol: "PPT",
AssocChain: "ETH",
ID: 1335,
Role: Token,
})
if err != nil {
t.Fatal(err)
}
@@ -377,35 +410,48 @@ func TestBaseCode(t *testing.T) {
t.Error("Expected 'Role undefined'")
}
main.Items[0].FullName = "Hello"
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat)
main.Items["CATS"][0].FullName = "Hello"
err = main.UpdateCurrency(&Item{
FullName: "MEWOW",
Symbol: "CATS",
ID: 1338,
Role: Fiat,
})
if err != nil {
t.Fatal(err)
}
if main.Items[0].FullName != "MEWOW" {
if main.Items["CATS"][0].FullName != "MEWOW" {
t.Error("Fullname not updated")
}
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Fiat)
if err != nil {
t.Fatal(err)
}
err = main.UpdateCurrency("WOWCATS", "CATS", "", 3, Fiat)
err = main.UpdateCurrency(&Item{
FullName: "WOWCATS",
Symbol: "CATS",
ID: 3,
Role: Fiat,
})
if err != nil {
t.Fatal(err)
}
// Creates a new item under a different currency role
if main.Items[0].ID != 3 {
if main.Items["CATS"][0].ID != 3 {
t.Error("ID not updated")
}
main.Items[0].Role = Unset
err = main.UpdateCurrency("MEWOW", "CATS", "", 1338, Cryptocurrency)
main.Items["CATS"][0].Role = Unset
err = main.UpdateCurrency(&Item{
FullName: "MEWOW",
Symbol: "CATS",
ID: 1338,
Role: Cryptocurrency,
})
if err != nil {
t.Fatal(err)
}
if main.Items[0].ID != 1338 {
if main.Items["CATS"][0].ID != 1338 {
t.Error("ID not updated")
}
}
@@ -507,29 +553,22 @@ func TestCodeMarshalJSON(t *testing.T) {
func TestIsFiatCurrency(t *testing.T) {
if EMPTYCODE.IsFiatCurrency() {
t.Errorf("TestIsFiatCurrency cannot match currency, %s.",
EMPTYCODE)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", EMPTYCODE)
}
if !USD.IsFiatCurrency() {
t.Errorf(
"TestIsFiatCurrency cannot match currency, %s.", USD)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", USD)
}
if !CNY.IsFiatCurrency() {
t.Errorf(
"TestIsFiatCurrency cannot match currency, %s.", CNY)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", CNY)
}
if LINO.IsFiatCurrency() {
t.Errorf(
"TestIsFiatCurrency cannot match currency, %s.", LINO,
)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", LINO)
}
if USDT.IsFiatCurrency() {
t.Errorf(
"TestIsFiatCurrency cannot match currency, %s.", USD)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", USDT)
}
if DAI.IsFiatCurrency() {
t.Errorf(
"TestIsFiatCurrency cannot match currency, %s.", USD)
t.Errorf("TestIsFiatCurrency cannot match currency, %s.", DAI)
}
}
@@ -595,3 +634,13 @@ func TestItemString(t *testing.T) {
&newItem)
}
}
// 28848025 40.84 ns/op 8 B/op 1 allocs/op // Current
//
// 546290 2192 ns/op 8 B/op 1 allocs/op // Previous
func BenchmarkNewCode(b *testing.B) {
b.ReportAllocs()
for x := 0; x < b.N; x++ {
_ = NewCode("someCode")
}
}

View File

@@ -28,7 +28,7 @@ type Role uint8
// BaseCodes defines a basket of bare currency codes
type BaseCodes struct {
Items []*Item
Items map[string][]*Item
LastMainUpdate time.Time
mtx sync.Mutex
}

View File

@@ -228,7 +228,14 @@ func (s *Storage) SetupConversionRates() {
// to the running list
func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error {
for i := range c {
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Fiat)
err := s.currencyCodes.UpdateCurrency(&Item{
ID: c[i].Item.ID,
FullName: c[i].Item.FullName,
Symbol: c[i].Item.Symbol,
Lower: c[i].Item.Lower,
Role: Fiat,
AssocChain: c[i].Item.AssocChain,
})
if err != nil {
return err
}
@@ -242,7 +249,14 @@ func (s *Storage) SetDefaultFiatCurrencies(c Currencies) error {
// list
func (s *Storage) SetStableCoins(c Currencies) error {
for i := range c {
err := s.currencyCodes.UpdateCurrency("", c[i].String(), "", 0, Stable)
err := s.currencyCodes.UpdateCurrency(&Item{
ID: c[i].Item.ID,
FullName: c[i].Item.FullName,
Symbol: c[i].Item.Symbol,
Lower: c[i].Item.Lower,
Role: Stable,
AssocChain: c[i].Item.AssocChain,
})
if err != nil {
return err
}
@@ -255,11 +269,14 @@ func (s *Storage) SetStableCoins(c Currencies) error {
// it to the running list
func (s *Storage) SetDefaultCryptocurrencies(c Currencies) error {
for i := range c {
err := s.currencyCodes.UpdateCurrency("",
c[i].String(),
"",
0,
Cryptocurrency)
err := s.currencyCodes.UpdateCurrency(&Item{
ID: c[i].Item.ID,
FullName: c[i].Item.FullName,
Symbol: c[i].Item.Symbol,
Lower: c[i].Item.Lower,
Role: Cryptocurrency,
AssocChain: c[i].Item.AssocChain,
})
if err != nil {
return err
}
@@ -464,33 +481,29 @@ func (s *Storage) LoadFileCurrencyData(f *File) error {
// UpdateCurrencies updates currency role and information using coin market cap
func (s *Storage) UpdateCurrencies() error {
m, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
currencyUpdates, err := s.currencyAnalysis.GetCryptocurrencyIDMap()
if err != nil {
return err
}
for x := range m {
if m[x].IsActive != 1 {
for x := range currencyUpdates {
if currencyUpdates[x].IsActive != 1 {
continue
}
if m[x].Platform.Symbol != "" {
err = s.currencyCodes.UpdateCurrency(m[x].Name,
m[x].Symbol,
m[x].Platform.Symbol,
m[x].ID,
Token)
if err != nil {
return err
}
continue
update := &Item{
FullName: currencyUpdates[x].Name,
Symbol: currencyUpdates[x].Symbol,
AssocChain: currencyUpdates[x].Platform.Symbol,
ID: currencyUpdates[x].ID,
Role: Cryptocurrency,
}
err = s.currencyCodes.UpdateCurrency(m[x].Name,
m[x].Symbol,
"",
m[x].ID,
Cryptocurrency)
if currencyUpdates[x].Platform.Symbol != "" {
update.Role = Token
}
err = s.currencyCodes.UpdateCurrency(update)
if err != nil {
return err
}