From 62a528a064c8ff927917dede4ed0a9ec3638f670 Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 4 Oct 2019 16:24:29 +1000 Subject: [PATCH] Bugfix/improvement: Orderbook/ticker update processing (#364) * Greatly increases efficiency of web socket orderbook processing by minimising multiple map lookups. Changes buffer to use pointers. Ensures orderbook benchmarks are on equal footing and set correctly. Removes data race by not setting waitgroup adds inside go routine causing wait and add to happen simultaneously. Updates ticker and orderbook service to use a pointer var instead of map lookups when setting data. * Removes misguided comment * Removes waitgroups and goroutines for updating bids and asks for non-id orderbook updates via websocket. Updates benchmarks to be consistent --- exchanges/orderbook/orderbook.go | 11 +- exchanges/ticker/ticker.go | 27 ++-- exchanges/websocket/wshandler/wshandler.go | 2 +- .../websocket/wshandler/wshandler_test.go | 3 +- .../websocket/wsorderbook/wsorderbook.go | 129 +++++++++--------- .../websocket/wsorderbook/wsorderbook_test.go | 115 +++++++++++++++- .../wsorderbook/wsorderbook_types.go | 2 +- 7 files changed, 197 insertions(+), 92 deletions(-) diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 2bdb4567..6396148f 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -95,11 +95,12 @@ func (s *Service) Update(b *Base) error { } default: - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Bids = b.Bids - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.Asks = b.Asks - s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].b.LastUpdated = b.LastUpdated - ids = s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Assoc - ids = append(ids, s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType].Main) + book := s.Books[b.ExchangeName][b.Pair.Base.Item][b.Pair.Quote.Item][b.AssetType] + book.b.Bids = b.Bids + book.b.Asks = b.Asks + book.b.LastUpdated = b.LastUpdated + ids = book.Assoc + ids = append(ids, book.Main) } s.Unlock() return s.mux.Publish(ids, b) diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 8b77f602..4ff779b6 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -147,19 +147,20 @@ func (s *Service) Update(p *Price) error { } default: - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Last = p.Last - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].High = p.High - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Low = p.Low - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Bid = p.Bid - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Ask = p.Ask - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Volume = p.Volume - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].QuoteVolume = p.QuoteVolume - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].PriceATH = p.PriceATH - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Open = p.Open - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Close = p.Close - s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].LastUpdated = p.LastUpdated - ids = s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Assoc - ids = append(ids, s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType].Main) + ticker := s.Tickers[p.ExchangeName][p.Pair.Base.Item][p.Pair.Quote.Item][p.AssetType] + ticker.Last = p.Last + ticker.High = p.High + ticker.Low = p.Low + ticker.Bid = p.Bid + ticker.Ask = p.Ask + ticker.Volume = p.Volume + ticker.QuoteVolume = p.QuoteVolume + ticker.PriceATH = p.PriceATH + ticker.Open = p.Open + ticker.Close = p.Close + ticker.LastUpdated = p.LastUpdated + ids = ticker.Assoc + ids = append(ids, ticker.Main) } s.Unlock() return s.mux.Publish(ids, p) diff --git a/exchanges/websocket/wshandler/wshandler.go b/exchanges/websocket/wshandler/wshandler.go index 1bc9502d..caa29869 100644 --- a/exchanges/websocket/wshandler/wshandler.go +++ b/exchanges/websocket/wshandler/wshandler.go @@ -92,6 +92,7 @@ func (w *Websocket) Connect() error { go w.connectionMonitor() } if w.SupportsFunctionality(WebsocketSubscribeSupported) || w.SupportsFunctionality(WebsocketUnsubscribeSupported) { + w.Wg.Add(1) go w.manageSubscriptions() } @@ -498,7 +499,6 @@ func (w *Websocket) manageSubscriptions() { w.DataHandler <- fmt.Errorf("%v does not support channel subscriptions, exiting ManageSubscriptions()", w.exchangeName) return } - w.Wg.Add(1) defer func() { if w.verbose { log.Debugf(log.WebsocketMgr, "%v ManageSubscriptions exiting", w.exchangeName) diff --git a/exchanges/websocket/wshandler/wshandler_test.go b/exchanges/websocket/wshandler/wshandler_test.go index 931ec661..dc293cf1 100644 --- a/exchanges/websocket/wshandler/wshandler_test.go +++ b/exchanges/websocket/wshandler/wshandler_test.go @@ -352,12 +352,13 @@ func TestManageSubscriptionsStartStop(t *testing.T) { ShutdownC: make(chan struct{}), Functionality: WebsocketSubscribeSupported | WebsocketUnsubscribeSupported, } + w.Wg.Add(1) go w.manageSubscriptions() close(w.ShutdownC) w.Wg.Wait() } -// TestManageSubscriptionsStartStop logic test +// TestManageSubscriptions logic test func TestManageSubscriptions(t *testing.T) { w := Websocket{ ShutdownC: make(chan struct{}), diff --git a/exchanges/websocket/wsorderbook/wsorderbook.go b/exchanges/websocket/wsorderbook/wsorderbook.go index 2422b921..e24a93ea 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook.go +++ b/exchanges/websocket/wsorderbook/wsorderbook.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "sort" - "sync" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -33,21 +32,22 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda } w.m.Lock() defer w.m.Unlock() - if _, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]; !ok { + obLookup, ok := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + if !ok { return fmt.Errorf("ob.Base could not be found for Exchange %s CurrencyPair: %s AssetType: %s", w.exchangeName, orderbookUpdate.CurrencyPair.String(), orderbookUpdate.AssetType) } if w.bufferEnabled { - overBufferLimit := w.processBufferUpdate(orderbookUpdate) + overBufferLimit := w.processBufferUpdate(obLookup, orderbookUpdate) if !overBufferLimit { return nil } } else { - w.processObUpdate(orderbookUpdate) + w.processObUpdate(obLookup, orderbookUpdate) } - err := w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Process() + err := obLookup.Process() if err != nil { return err } @@ -58,144 +58,142 @@ func (w *WebsocketOrderbookLocal) Update(orderbookUpdate *WebsocketOrderbookUpda return nil } -func (w *WebsocketOrderbookLocal) processBufferUpdate(orderbookUpdate *WebsocketOrderbookUpdate) bool { +func (w *WebsocketOrderbookLocal) processBufferUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) bool { if w.buffer == nil { - w.buffer = make(map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate) + w.buffer = make(map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate) } if w.buffer[orderbookUpdate.CurrencyPair] == nil { - w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]WebsocketOrderbookUpdate) + w.buffer[orderbookUpdate.CurrencyPair] = make(map[asset.Item][]*WebsocketOrderbookUpdate) } - if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) <= w.obBufferLimit { - w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = append(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], *orderbookUpdate) - if len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]) < w.obBufferLimit { + bufferLookup := w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] + if len(bufferLookup) <= w.obBufferLimit { + bufferLookup = append(bufferLookup, orderbookUpdate) + if len(bufferLookup) < w.obBufferLimit { + w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup return false } } if w.sortBuffer { // sort by last updated to ensure each update is in order if w.sortBufferByUpdateIDs { - sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool { - return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateID < w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateID + sort.Slice(bufferLookup, func(i, j int) bool { + return bufferLookup[i].UpdateID < bufferLookup[j].UpdateID }) } else { - sort.Slice(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType], func(i, j int) bool { - return w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i].UpdateTime.Before(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][j].UpdateTime) + sort.Slice(bufferLookup, func(i, j int) bool { + return bufferLookup[i].UpdateTime.Before(bufferLookup[j].UpdateTime) }) } } - for i := 0; i < len(w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType]); i++ { - w.processObUpdate(&w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType][i]) + for i := 0; i < len(bufferLookup); i++ { + w.processObUpdate(o, bufferLookup[i]) } + w.buffer[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType] = bufferLookup return true } -func (w *WebsocketOrderbookLocal) processObUpdate(orderbookUpdate *WebsocketOrderbookUpdate) { +func (w *WebsocketOrderbookLocal) processObUpdate(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { if w.updateEntriesByID { - w.updateByIDAndAction(orderbookUpdate) + w.updateByIDAndAction(o, orderbookUpdate) } else { - var wg sync.WaitGroup - wg.Add(2) - go w.updateAsksByPrice(orderbookUpdate, &wg) - go w.updateBidsByPrice(orderbookUpdate, &wg) - wg.Wait() + w.updateAsksByPrice(o, orderbookUpdate) + w.updateBidsByPrice(o, orderbookUpdate) } } -func (w *WebsocketOrderbookLocal) updateAsksByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) { +func (w *WebsocketOrderbookLocal) updateAsksByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { for j := 0; j < len(base.Asks); j++ { found := false - for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Asks); k++ { - if w.ob[base.CurrencyPair][base.AssetType].Asks[k].Price == base.Asks[j].Price { + for k := 0; k < len(o.Asks); k++ { + if o.Asks[k].Price == base.Asks[j].Price { found = true if base.Asks[j].Amount == 0 { - w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks[:k], - w.ob[base.CurrencyPair][base.AssetType].Asks[k+1:]...) + o.Asks = append(o.Asks[:k], + o.Asks[k+1:]...) break } - w.ob[base.CurrencyPair][base.AssetType].Asks[k].Amount = base.Asks[j].Amount + o.Asks[k].Amount = base.Asks[j].Amount break } } if !found { - w.ob[base.CurrencyPair][base.AssetType].Asks = append(w.ob[base.CurrencyPair][base.AssetType].Asks, base.Asks[j]) + o.Asks = append(o.Asks, base.Asks[j]) } } - sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Asks, func(i, j int) bool { - return w.ob[base.CurrencyPair][base.AssetType].Asks[i].Price < w.ob[base.CurrencyPair][base.AssetType].Asks[j].Price + sort.Slice(o.Asks, func(i, j int) bool { + return o.Asks[i].Price < o.Asks[j].Price }) - wg.Done() } -func (w *WebsocketOrderbookLocal) updateBidsByPrice(base *WebsocketOrderbookUpdate, wg *sync.WaitGroup) { +func (w *WebsocketOrderbookLocal) updateBidsByPrice(o *orderbook.Base, base *WebsocketOrderbookUpdate) { for j := 0; j < len(base.Bids); j++ { found := false - for k := 0; k < len(w.ob[base.CurrencyPair][base.AssetType].Bids); k++ { - if w.ob[base.CurrencyPair][base.AssetType].Bids[k].Price == base.Bids[j].Price { + for k := 0; k < len(o.Bids); k++ { + if o.Bids[k].Price == base.Bids[j].Price { found = true if base.Bids[j].Amount == 0 { - w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids[:k], - w.ob[base.CurrencyPair][base.AssetType].Bids[k+1:]...) + o.Bids = append(o.Bids[:k], + o.Bids[k+1:]...) break } - w.ob[base.CurrencyPair][base.AssetType].Bids[k].Amount = base.Bids[j].Amount + o.Bids[k].Amount = base.Bids[j].Amount break } } if !found { - w.ob[base.CurrencyPair][base.AssetType].Bids = append(w.ob[base.CurrencyPair][base.AssetType].Bids, base.Bids[j]) + o.Bids = append(o.Bids, base.Bids[j]) } } - sort.Slice(w.ob[base.CurrencyPair][base.AssetType].Bids, func(i, j int) bool { - return w.ob[base.CurrencyPair][base.AssetType].Bids[i].Price > w.ob[base.CurrencyPair][base.AssetType].Bids[j].Price + sort.Slice(o.Bids, func(i, j int) bool { + return o.Bids[i].Price > o.Bids[j].Price }) - wg.Done() } // updateByIDAndAction will receive an action to execute against the orderbook // it will then match by IDs instead of price to perform the action -func (w *WebsocketOrderbookLocal) updateByIDAndAction(orderbookUpdate *WebsocketOrderbookUpdate) { +func (w *WebsocketOrderbookLocal) updateByIDAndAction(o *orderbook.Base, orderbookUpdate *WebsocketOrderbookUpdate) { switch orderbookUpdate.Action { case "update": for _, target := range orderbookUpdate.Bids { - for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].Amount = target.Amount + for i := range o.Bids { + if o.Bids[i].ID == target.ID { + o.Bids[i].Amount = target.Amount break } } } for _, target := range orderbookUpdate.Asks { - for i := range w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].Amount = target.Amount + for i := range o.Asks { + if o.Asks[i].ID == target.ID { + o.Asks[i].Amount = target.Amount break } } } case "delete": for _, target := range orderbookUpdate.Bids { - for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids); i++ { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[:i], - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids[i+1:]...) + for i := 0; i < len(o.Bids); i++ { + if o.Bids[i].ID == target.ID { + o.Bids = append(o.Bids[:i], + o.Bids[i+1:]...) i-- break } } } for _, target := range orderbookUpdate.Asks { - for i := 0; i < len(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks); i++ { - if w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i].ID == target.ID { - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[:i], - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks[i+1:]...) + for i := 0; i < len(o.Asks); i++ { + if o.Asks[i].ID == target.ID { + o.Asks = append(o.Asks[:i], + o.Asks[i+1:]...) i-- break } } } case "insert": - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Bids, orderbookUpdate.Bids...) - w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks = append(w.ob[orderbookUpdate.CurrencyPair][orderbookUpdate.AssetType].Asks, orderbookUpdate.Asks...) + o.Bids = append(o.Bids, orderbookUpdate.Bids...) + o.Asks = append(o.Asks, orderbookUpdate.Asks...) } } @@ -227,10 +225,11 @@ func (w *WebsocketOrderbookLocal) LoadSnapshot(newOrderbook *orderbook.Base) err if w.ob[newOrderbook.Pair] == nil { w.ob[newOrderbook.Pair] = make(map[asset.Item]*orderbook.Base) } - if w.ob[newOrderbook.Pair][newOrderbook.AssetType] != nil && - (len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Asks) > 0 || - len(w.ob[newOrderbook.Pair][newOrderbook.AssetType].Bids) > 0) { - w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook + fullObLookup := w.ob[newOrderbook.Pair][newOrderbook.AssetType] + if fullObLookup != nil && + (len(fullObLookup.Asks) > 0 || + len(fullObLookup.Bids) > 0) { + fullObLookup = newOrderbook return newOrderbook.Process() } w.ob[newOrderbook.Pair][newOrderbook.AssetType] = newOrderbook diff --git a/exchanges/websocket/wsorderbook/wsorderbook_test.go b/exchanges/websocket/wsorderbook/wsorderbook_test.go index 8c0c09db..a60fd067 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_test.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_test.go @@ -43,6 +43,61 @@ func createSnapshot() (obl *WebsocketOrderbookLocal, curr currency.Pair, asks, b return } +func bidAskGenerator() []orderbook.Item { + var response []orderbook.Item + randIterator := 100 + for i := 0; i < randIterator; i++ { + price := float64(rand.Intn(1000)) + if price == 0 { + price = 1 + } + response = append(response, orderbook.Item{ + Amount: float64(rand.Intn(1)), + Price: price, + ID: int64(i), + }) + } + return response +} + +func BenchmarkUpdateBidsByPrice(b *testing.B) { + ob, curr, _, _, err := createSnapshot() + if err != nil { + b.Error(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + bidAsks := bidAskGenerator() + update := &WebsocketOrderbookUpdate{ + Bids: bidAsks, + Asks: bidAsks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + ob.updateBidsByPrice(ob.ob[curr][asset.Spot], update) + } +} + +func BenchmarkUpdateAsksByPrice(b *testing.B) { + ob, curr, _, _, err := createSnapshot() + if err != nil { + b.Error(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + bidAsks := bidAskGenerator() + update := &WebsocketOrderbookUpdate{ + Bids: bidAsks, + Asks: bidAsks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + ob.updateAsksByPrice(ob.ob[curr][asset.Spot], update) + } +} + // BenchmarkBufferPerformance demonstrates buffer more performant than multi // process calls func BenchmarkBufferPerformance(b *testing.B) { @@ -50,6 +105,42 @@ func BenchmarkBufferPerformance(b *testing.B) { if err != nil { b.Fatal(err) } + obl.bufferEnabled = true + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) + update := &WebsocketOrderbookUpdate{ + Bids: bids, + Asks: asks, + CurrencyPair: curr, + UpdateTime: time.Now(), + AssetType: asset.Spot, + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + randomIndex := rand.Intn(4) + update.Asks = itemArray[randomIndex] + update.Bids = itemArray[randomIndex] + err = obl.Update(update) + if err != nil { + b.Fatal(err) + } + } +} + +// BenchmarkBufferSortingPerformance benchmark +func BenchmarkBufferSortingPerformance(b *testing.B) { + obl, curr, asks, bids, err := createSnapshot() + if err != nil { + b.Fatal(err) + } + obl.bufferEnabled = true obl.sortBuffer = true cp := currency.NewPairFromString("BTCUSD") // This is to ensure we do not send in zero orderbook info to our main book @@ -67,8 +158,9 @@ func BenchmarkBufferPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) @@ -79,14 +171,23 @@ func BenchmarkBufferPerformance(b *testing.B) { } // BenchmarkBufferSortingPerformance benchmark -func BenchmarkBufferSortingPerformance(b *testing.B) { +func BenchmarkBufferSortingByIDPerformance(b *testing.B) { obl, curr, asks, bids, err := createSnapshot() if err != nil { b.Fatal(err) } - obl.sortBuffer = true obl.bufferEnabled = true - obl.obBufferLimit = 5 + obl.sortBuffer = true + obl.sortBufferByUpdateIDs = true + cp := currency.NewPairFromString("BTCUSD") + // This is to ensure we do not send in zero orderbook info to our main book + // in orderbook.go, orderbooks should not be zero even after an update. + dummyItem := orderbook.Item{ + Amount: 1333337, + Price: 1337.1337, + ID: 1337, + } + obl.ob[cp][asset.Spot].Bids = append(obl.ob[cp][asset.Spot].Bids, dummyItem) update := &WebsocketOrderbookUpdate{ Bids: bids, Asks: asks, @@ -94,8 +195,9 @@ func BenchmarkBufferSortingPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) @@ -128,8 +230,9 @@ func BenchmarkNoBufferPerformance(b *testing.B) { UpdateTime: time.Now(), AssetType: asset.Spot, } + b.ResetTimer() for i := 0; i < b.N; i++ { - randomIndex := rand.Intn(5) + randomIndex := rand.Intn(4) update.Asks = itemArray[randomIndex] update.Bids = itemArray[randomIndex] err = obl.Update(update) diff --git a/exchanges/websocket/wsorderbook/wsorderbook_types.go b/exchanges/websocket/wsorderbook/wsorderbook_types.go index 8fa4f35c..6766c797 100644 --- a/exchanges/websocket/wsorderbook/wsorderbook_types.go +++ b/exchanges/websocket/wsorderbook/wsorderbook_types.go @@ -13,7 +13,7 @@ import ( // appending and deleting changes and updates the main store in wsorderbook.go type WebsocketOrderbookLocal struct { ob map[currency.Pair]map[asset.Item]*orderbook.Base - buffer map[currency.Pair]map[asset.Item][]WebsocketOrderbookUpdate + buffer map[currency.Pair]map[asset.Item][]*WebsocketOrderbookUpdate obBufferLimit int bufferEnabled bool sortBuffer bool