XAML Playground
about XAML and other Amenities

TrailWithcount and TrailWithTime: the regular way but with performance impact

2011-04-01T14:19:47+01:00 by Andrea Boschin

Today @saldoukhov pointed me to an alternative (and possibly regular) way to implement TrailWithCount, different from the version I introduced yesterday in my latest post. This tecnique invole the use of a bunch of methods from the Reactive Extensions and, at first sight may result more simple and straightforward. Also if @saldoukhov pointed to the sole count version it is also possibile to write a TrailWithTime this way. Here is the new implementation of these methods:

   1: // BE AWARE THAT THESE METHODS HAVE HUGE PERFORMANCE IMPACT
   2:  
   3: public static IObservable<IEnumerable<T>> TrailWithCount<T>(this IObservable<T> observable, int count)
   4: {
   5:     return observable.Scan(
   6:         Enumerable.Empty<T>(),
   7:         (a, b) => a.StartWith(b).Take(count));
   8: }
   9:  
  10: public static IObservable<IEnumerable<T>> TrailWithTime<T>(this IObservable<T> observable, TimeSpan timeSpan)
  11: {
  12:     return observable
  13:         .Select(o => new Timestamped<T>(o, DateTime.Now))
  14:         .Scan(
  15:             Enumerable.Empty<Timestamped<T>>(), (a, b) =>
  16:                 a.StartWith(b).TakeWhile(o => (DateTime.Now - o.Timestamp).TotalMilliseconds <= timeSpan.TotalMilliseconds))
  17:         .Select(k => k.Select(o3 => o3.Value));
  18: }

Both the methods work the same way with the sole difference that the TrailWithTime wraps the Point in a Timestamped instance and then unwraps it just before returning to the caller. Here the Scan method is an aggregation function that forward every occurrence from the stream and populate a resulting aggregation. So the StartWith method let the aggregation grow and then the Take method extract the "counted" instances we really need.

The problem here is that if you try to run this method on a very large number of items (e.g. 1000) you will see a huge performance impact that leave the start of the trail far from the mouse pointer. The effect is so far more evident if you try to use the TrailWithTime with a long timeout (e.g. 5/10 sec).

As far as I understood these methods have an huge payload in terms of iterations and of garbage they produce. Every time you get an event on the stream a new instance of the array is created and it is crawled to create the result. The TrailWithTime has the worst performances because it produces a great number of Timestamped<> instances. I'm not aware of the inner working of the Scan, StartWith and Take methods but the resulting effect is really clear

If you try to run my previous version with a similar interval you will see a very tiny performance impact. Internally I use a queue and the sole payload are the collection of the Dequeued items (but they would have been collected the same if there is not the trail) and the iteration along the resulting collection to create a PointCollection. This is an interesting demonstration of how the use of LINQ may affect drammatically the performances of an application and I suggest you to double check when you use them.

By the way, thanks to @saldoukhov for his suggestion that I had not considered when I wrote for the first time my Trail methods.