@@ -117,8 +117,10 @@ 7. Performance Considerations
117117using OxyPlot . Series ;
118118using Prism . Mvvm ;
119119using System ;
120+ using System . Buffers ;
120121using System . Collections . Generic ;
121122using System . Collections . ObjectModel ;
123+ using System . Collections . Specialized ;
122124using System . Linq ;
123125using System . Windows ;
124126using System . Windows . Threading ;
@@ -127,6 +129,7 @@ 7. Performance Considerations
127129using VisualHFT . Enums ;
128130using VisualHFT . Helpers ;
129131using VisualHFT . Model ;
132+ using Windows . Foundation . Collections ;
130133using AxisPosition = OxyPlot . Axes . AxisPosition ;
131134
132135
@@ -140,11 +143,6 @@ public class vmOrderBook : BindableBase, IDisposable
140143 private readonly TimeSpan _MIN_UI_REFRESH_TS = TimeSpan . FromMilliseconds ( 60 ) ; //For the UI: do not allow less than this, since it is not noticeble for human eye
141144
142145
143- private static class OrderBookSnapshotPool
144- {
145- public static readonly CustomObjectPool < OrderBookSnapshot > Instance = new
146- CustomObjectPool < OrderBookSnapshot > ( maxPoolSize : _MAX_CHART_POINTS + ( int ) ( _MAX_CHART_POINTS * 1.1 ) ) ;
147- }
148146 private static class ScatterPointsPool
149147 {
150148 public static readonly CustomObjectPool < OxyPlot . Series . ScatterPoint > Instance =
@@ -165,11 +163,9 @@ private static class ScatterPointsListPool
165163 private string _selectedSymbol ;
166164 private VisualHFT . ViewModel . Model . Provider _selectedProvider = null ;
167165 private AggregationLevel _aggregationLevelSelection ;
168-
169166 private List < BookItem > _bidsGrid ;
170167 private List < BookItem > _asksGrid ;
171168 private CachedCollection < BookItem > _depthGrid ;
172-
173169 private ObservableCollection < VisualHFT . ViewModel . Model . Provider > _providers ;
174170
175171 private BookItem _AskTOB = new BookItem ( ) ;
@@ -568,6 +564,7 @@ private void uiUpdaterAction()
568564 RaisePropertyChanged ( nameof ( Spread ) ) ;
569565 RaisePropertyChanged ( nameof ( LOBImbalanceValue ) ) ;
570566
567+
571568 RaisePropertyChanged ( nameof ( Bids ) ) ;
572569 RaisePropertyChanged ( nameof ( Asks ) ) ;
573570 RaisePropertyChanged ( nameof ( Depth ) ) ;
@@ -630,40 +627,33 @@ private void LIMITORDERBOOK_OnDataReceived(OrderBook e)
630627
631628
632629 e . CalculateMetrics ( ) ;
633- OrderBookSnapshot snapshot = OrderBookSnapshotPool . Instance . Get ( ) ;
630+ // ✅ CHANGED: Use struct factory method instead of pool
631+ var snapshot = OrderBookSnapshot . Create ( ) ;
634632 // Initialize its state based on the master OrderBook.
635633 snapshot . UpdateFrom ( e ) ;
636634 // Enqueue for processing.
637635 _QUEUE . Add ( snapshot ) ;
638636 }
639637 private void _AGGREGATED_LOB_OnRemoving ( object ? sender , OrderBookSnapshot e )
640638 {
641- // Perform cleanup BEFORE returning the object to the pool
639+ // Perform cleanup BEFORE disposing the arrays
642640 lock ( RealTimeSpreadModel . SyncRoot )
643641 RemoveLastPointToSpreadChart ( ) ;
644642 lock ( RealTimePricePlotModel . SyncRoot )
645643 RemoveLastPointsToScatterChart ( ) ;
646644
647- // NOW it is safe to return the object to the pool
648- OrderBookSnapshotPool . Instance . Return ( e ) ;
645+ // ✅ CHANGED: Dispose to return arrays to pool (idempotent, safe to call multiple times)
646+ e . Dispose ( ) ;
647+
648+ // ❌ REMOVED: No longer needed - struct doesn't pool itself
649+ // OrderBookSnapshotPool.Instance.Return(e);
649650 }
650651 private void _AGGREGATED_LOB_OnRemoved ( object ? sender , int index )
651652 {
652- //for current snapshot, make sure to return to the pool
653- if ( index == - 1 )
654- OrderBookSnapshotPool . Instance . Reset ( ) ; //reset the entire pool
653+ // ❌ REMOVED: Struct snapshots don't need pool reset
655654
656- //remove last points on the chart
657- if ( index == 0 ) //make sure the item is the last
658- {
659- // This logic is now moved to _AGGREGATED_LOB_OnRemoving
660- /*
661- lock (RealTimeSpreadModel.SyncRoot)
662- RemoveLastPointToSpreadChart();
663- lock (RealTimePricePlotModel.SyncRoot)
664- RemoveLastPointsToScatterChart();
665- */
666- }
655+ // Note: Cleanup is now handled in _AGGREGATED_LOB_OnRemoving via Dispose()
656+ // which is called BEFORE the item is removed from the collection
667657 }
668658
669659
@@ -700,7 +690,8 @@ private void QUEUE_onReadAction(OrderBookSnapshot ob)
700690
701691 if ( ! addedOK )
702692 {
703- OrderBookSnapshotPool . Instance . Return ( ob ) ;
693+ // ✅ CHANGED: Dispose snapshot (returns arrays to pool) if not added
694+ ob . Dispose ( ) ;
704695 return ;
705696 }
706697
@@ -724,13 +715,16 @@ private void QUEUE_onReadAction(OrderBookSnapshot ob)
724715 }
725716
726717 // ✅ PHASE 4: Create scatter points OUTSIDE ANY LOCKS
718+ // Convert spans to arrays for filtering (temporary allocations, but minimal)
719+ var bidsArray = lobItemToDisplay . Bids . ToArray ( ) ;
720+ var asksArray = lobItemToDisplay . Asks . ToArray ( ) ;
721+
727722 var bidLevelPoints = ToScatterPointsLevels (
728- lobItemToDisplay . Bids . Where ( x => x . Price >= _MidPoint * 0.99 ) ,
723+ bidsArray . Where ( x => x . Price >= _MidPoint * 0.99 ) . ToArray ( ) ,
729724 sharedTS ) ;
730725 var askLevelPoints = ToScatterPointsLevels (
731- lobItemToDisplay . Asks . Where ( x => x . Price <= _MidPoint * 1.01 ) ,
726+ asksArray . Where ( x => x . Price <= _MidPoint * 1.01 ) . ToArray ( ) ,
732727 sharedTS ) ;
733-
734728 try
735729 {
736730 // ✅ PHASE 5: Update scatter chart (Level 3 lock)
@@ -768,15 +762,15 @@ private void QUEUE_onErrorAction(Exception ex)
768762
769763 private DataPoint ? ToDataPointBestBid ( OrderBookSnapshot ? lob , double sharedTS )
770764 {
771- if ( lob != null && lob . Bids != null && lob . Bids . Count > 0 && lob . Bids [ 0 ] . Price . HasValue )
772- return new DataPoint ( sharedTS , lob . Bids [ 0 ] . Price . Value ) ;
765+ if ( lob . HasValue && lob . Value . Bids . Length > 0 && lob . Value . Bids [ 0 ] ? . Price . HasValue == true )
766+ return new DataPoint ( sharedTS , lob . Value . Bids [ 0 ] . Price . Value ) ;
773767 else
774768 return null ;
775769 }
776770 private DataPoint ? ToDataPointBestAsk ( OrderBookSnapshot ? lob , double sharedTS )
777771 {
778- if ( lob != null && lob . Asks != null && lob . Asks . Count > 0 && lob . Asks [ 0 ] . Price . HasValue )
779- return new DataPoint ( sharedTS , lob . Asks [ 0 ] . Price . Value ) ;
772+ if ( lob . HasValue && lob . Value . Asks . Length > 0 && lob . Value . Asks [ 0 ] ? . Price . HasValue == true )
773+ return new DataPoint ( sharedTS , lob . Value . Asks [ 0 ] . Price . Value ) ;
780774 else
781775 return null ;
782776 }
@@ -789,10 +783,10 @@ private DataPoint ToDataPointSpread(OrderBookSnapshot lob, double sharedTS)
789783 return new DataPoint ( sharedTS , lob . Spread ) ;
790784 }
791785 private List < OxyPlot . Series . ScatterPoint > ToScatterPointsLevels (
792- IEnumerable < BookItem > lobList ,
786+ ReadOnlySpan < BookItem > lobSpan ,
793787 double sharedTS )
794788 {
795- if ( lobList == null || ! lobList . Any ( ) )
789+ if ( lobSpan . IsEmpty )
796790 {
797791 var emptyList = ScatterPointsListPool . Instance . Get ( ) ;
798792 emptyList . Clear ( ) ;
@@ -803,6 +797,9 @@ private DataPoint ToDataPointSpread(OrderBookSnapshot lob, double sharedTS)
803797 var scatterPoints = ScatterPointsListPool . Instance . Get ( ) ;
804798 scatterPoints . Clear ( ) ;
805799
800+ // Convert span to array for LINQ operations (temporary allocation)
801+ var lobList = lobSpan . ToArray ( ) ;
802+
806803 // Size normalization logic
807804 _minScatterBubbleSize = Math . Min ( _minScatterBubbleSize , lobList . Min ( x => x . Size . Value ) ) ;
808805 _maxScatterBubbleSize = Math . Max ( _maxScatterBubbleSize , lobList . Max ( x => x . Size . Value ) ) ;
@@ -829,9 +826,13 @@ private DataPoint ToDataPointSpread(OrderBookSnapshot lob, double sharedTS)
829826 }
830827 return scatterPoints ; // ← List will be returned to pool, ScatterPoints will live in chart
831828 }
832- private IEnumerable < DataPoint > ToDataPointsCumulativeVolume ( List < BookItem > lobList , double sharedTS )
829+ private IEnumerable < DataPoint > ToDataPointsCumulativeVolume ( ReadOnlySpan < BookItem > lobSpan , double sharedTS )
833830 {
834- var retItems = new List < DataPoint > ( lobList . Count ) ;
831+ if ( lobSpan . IsEmpty )
832+ return Enumerable . Empty < DataPoint > ( ) ;
833+
834+ var lobList = lobSpan . ToArray ( ) ; // Temporary conversion for processing
835+ var retItems = new List < DataPoint > ( lobList . Length ) ;
835836 double cumulativeVol = 0 ;
836837
837838 foreach ( var level in lobList )
@@ -1085,7 +1086,6 @@ private void Clear()
10851086 _AGGREGATED_LOB . OnRemoving += _AGGREGATED_LOB_OnRemoving ;
10861087 }
10871088
1088- OrderBookSnapshotPool . Instance . Reset ( ) ; //reset the entire pool
10891089 ScatterPointsPool . Instance . Reset ( ) ;
10901090
10911091 Dispatcher . CurrentDispatcher . BeginInvoke ( ( ) =>
@@ -1110,7 +1110,6 @@ private void Clear()
11101110
11111111 }
11121112
1113-
11141113 /// <summary>
11151114 /// Bids the ask grid update.
11161115 /// Update our internal lists trying to re-use the current items on the list.
@@ -1119,29 +1118,16 @@ private void Clear()
11191118 /// <param name="orderBook">The order book.</param>
11201119 private void BidAskGridUpdate ( OrderBookSnapshot orderBook )
11211120 {
1122- if ( orderBook == null )
1123- return ;
1124-
11251121 GridListUpdate ( _asksGrid , orderBook . Asks ) ;
11261122 GridListUpdate ( _bidsGrid , orderBook . Bids ) ;
1127-
1128- //commented out for now
1129- /*if (_asksGrid != null && _bidsGrid != null)
1130- {
1131- _depthGrid.Clear();
1132- foreach (var item in _asksGrid)
1133- _depthGrid.Add(item);
1134- foreach (var item in _bidsGrid)
1135- _depthGrid.Add(item);
1136- }*/
11371123 }
11381124
1139- private void GridListUpdate ( List < BookItem > currentList , List < BookItem > newList )
1125+ private void GridListUpdate ( List < BookItem > currentList , ReadOnlySpan < BookItem > newList )
11401126 {
11411127 // Update existing items and add/remove as needed
1142- for ( int i = 0 ; i < Math . Max ( currentList . Count , newList . Count ) ; i ++ )
1128+ for ( int i = 0 ; i < Math . Max ( currentList . Count , newList . Length ) ; i ++ )
11431129 {
1144- if ( i < newList . Count )
1130+ if ( i < newList . Length )
11451131 {
11461132 if ( i < currentList . Count )
11471133 UpdateBookItem ( currentList [ i ] , newList [ i ] ) ; // Update existing item
@@ -1271,7 +1257,7 @@ public IReadOnlyList<BookItem> Bids
12711257 public PlotModel RealTimePricePlotModel { get ; set ; }
12721258 public PlotModel RealTimeSpreadModel { get ; set ; }
12731259 public PlotModel CummulativeBidsChartModel { get ; set ; }
1274- public PlotModel CummulativeAsksChartModel { get ; set ; }
1260+ public PlotModel CummulativeAsksChartModel { get ; }
12751261
12761262
12771263 public int SwitchView
@@ -1299,11 +1285,10 @@ protected virtual void Dispose(bool disposing)
12991285 _dialogs = null ;
13001286 _realTimeTrades ? . Clear ( ) ;
13011287 _depthGrid ? . Clear ( ) ;
1288+ _providers ? . Clear ( ) ;
13021289 _bidsGrid ? . Clear ( ) ;
13031290 _asksGrid ? . Clear ( ) ;
1304- _providers ? . Clear ( ) ;
13051291 ScatterPointsPool . Instance . Dispose ( ) ;
1306- OrderBookSnapshotPool . Instance . Dispose ( ) ;
13071292 ScatterPointsListPool . Instance . Dispose ( ) ;
13081293 _QUEUE ? . Dispose ( ) ;
13091294
0 commit comments