SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, and iOS Chart & Android Chart Components

0
0

Hi,

I develop a real time app that polls online data, e.g. EEG, and sends it on a SciChart plot. I need to keep the polling thread at maximal and steady time resolution, ideally, close to 1 ms. To achieve that, I run the Scichart plot in a different STA thread (WPF window), such that the polling thread uses only IDataSeries.Append() and IDataSeries. SuspendUpdates() methods to submit data to the plot.

However, time tests show that these 2 functions in the polling thread have very irregular execution times, which may depend on different factors, e.g. on the plot size. To demonstrate it, I recorded the calling times of the following code, which submits 10 000 samples in a loop:

            double StartTimeInMs = SW.ElapsedTicks / 10000;
            using (TestDataSeries.SuspendUpdates())                 
                    {
                    TestDataSeries.Clear();
                    TestDataSeries.Append(X, Y);
                    }
            TimeDelta = SW.ElapsedTicks / 10000 - StartTimeInMs;

The resulting TimeDelta (loop times) are shown on the the plot 1 in the attachment.As you can see the loop time is unstable and grows to 40 ms, when the plot window is maximized.

I came to a possible solution by invoking the sample submission code in the plot thread:

            double StartTimeInMs = SW.ElapsedTicks / 10000;             
            Dispatcher D = (TestDataSeries.ParentSurface as SciChartSurface).Dispatcher;
            D.BeginInvoke((Action)(() =>
                {
                    using (TestDataSeries.SuspendUpdates())
                        {
                        TestDataSeries.Clear();
                        TestDataSeries.Append(X, Y);
                        }
                }));
            TimeDelta = SW.ElapsedTicks / 10000 - StartTimeInMs;

As you can on the attached plot 2, the loop times are much better now, although I still see occasional peaks above 5 ms.

So, my questions are:

  1. Why the timing in my tests is so unstable?
  2. Are the particular methods or settings to stabilize the time required to submit new samples to a Scichart plot?
  3. Are the FIFO series more stable in timing?
  4. If I use the BeginInvoke in a separate plot thread, should I use lock() to avoid potential data overflow, for instance:

                Dispatcher D = (TestDataSeries.ParentSurface as SciChartSurface).Dispatcher;
            D.BeginInvoke((Action)(() =>
                {
                if (Monitor.TryEnter(TestDataSeries.SyncRoot, new TimeSpan(0, 0, 0, 0, 0)))
                    try
                        {
                        TestDataSeries.Clear();
                        TestDataSeries.Append(X, Y);
                        }
                    finally
                        {
                        Monitor.Exit(TestDataSeries.SyncRoot);
                        }
                }));
    

Thank you in advance for answers and hints!

Version
6.2.1
Images
  • You must to post comments
0
0

As per our Performance Tips and Tricks FAQ, calling DataSeries.Append(x,y) with a single point thousands of times is not optimal for performance.

What happens inside DataSeries.Append is as follows:

  1. We perform a lock() around a syncroot object which halts rendering. If rendering is occuring now, we wait until its finished (hence your variable time) then lock(). All threads accessing the dataseries and same SciChartSurface and UI thread are in this synchronisation
  2. We append the single point, resizing arrays if necessary
  3. We check if the single point contains NaN, if the data is still sorted ascending and calculate some other flags used later in correct rendering & algorithm choosing
  4. DataSeries raises ‘DataSeriesChanged’ event
  5. SciChartSurface sets a flag that it needs to be redraw at next render event

If you append a single x,y point then all the above has to be performed per-point. It’s very efficient, takes a very short period of time, but when you have a background thread hitting one or more DataSeries.Append() calls then you can quickly reach lock contention which can cause performance problems.

The solution from our documentation is as follows:

Batching appends using the overloaded API to append IEnumerable, IList or Arrays has a massive impact on DataSeries performance. Arrays have the biggest impact as these can be indexed using unsafe code. If you have a lot of data to append, create a small buffer (say 10-100 points) and append them in blocks.

NOTE: Why does this work? When data is appended in blocks, you get a single draw call at the end. You also reduce thread-contention if you are appending on a background thread and drawing on the UI thread. Finally, the above is simply more memory efficient as we only need to recalculate the additional memory required once, rather than per-point appended.

SciChart processes data so fast, we can process >100,000 updates to the DataSeries per second. However, if you update that fast, then > SciChart will spend a lot of time in static overhead for dataseries updates. It’s better for performance to update more data, less often.

Please let me know if this helps!

Best regards,
Andrew

  • You must to post comments
0
0

We perform a lock() around a syncroot object which halts rendering. If rendering is occuring now, we wait until its finished (hence your variable time) then lock()

That can indeed explain the variable time, thanks !

You also reduce thread-contention if you are appending on a background thread and drawing on the UI thread

The multi-threading seems a reasonable solution but my question is slightly different. To rephrase it, given that I append data in a separate thread, how can I stabilize calling times of Append() in that thread? As you can see on the plot 2, there are still occasional 5 ms peaks in the calling times, while the majorly of them are within 1 ms. Is there a better solution than using BeginInvoke? Are there some settings that can make the calling times more stable, rather than just faster?

  • corvex
    I’ve tested another option, with an async task: Task.Run(() => { using (TestDataSeries.SuspendUpdates()) { TestDataSeries.Clear(); TestDataSeries.Append(X, Y); } }); and found that the timing is stable as with BeginInvoke. However, with Task.Run, the plot flicks on updates despite of the SuspendUpdates lock. In your option, should I use BeginInvoke or Task.Run?
  • Andrew Burnett-Thompson
    Hi there, you can’t really. Windows is like that, timings are never perfectly exact and there will always be jitter between events. You don’t need to use BeginInvoke at all when appending to DataSeries, and you don’t need to use Monitor.Enter. If you check our examples we don’t do either of these. You can append straight from a background thread. Have a look at https://www.scichart.com/example/wpf-chart-example-performance-demo/ for guidance! This example appends on a background thread, uses a buffer, uses SuspendUpdates() because its updating multiple series, and does not call Monitor.Enter or Dispatcher.BeginInvoke.
  • corvex
    Hi Andrew, In the latest Scatter Chart Performance demo, there is TimedMethod helper class that, in my understanding, initiates appending data in a background thread. What is the different to the TimedMethod to the async Task and BeginInvoke?
  • You must to post comments
0
0

Apart from our Performance Tips and Tricks documentation, we have a number of examples which show you best practice in using SciChart in a high performance scenario.

Take a look at the WPF Chart Realtime performance demo

enter image description here

In this example

  • We use System.Timers.Timer, which fires an event on a background thread
  • We have a small buffer to append points so that we use the DataSeries.Append(double[] xValues, double[] yValues) overload which is faster for reasons I said above
  • We don’t use Monitor.Enter or locking around the DataSeries. SciChart does that for you
  • We do use .SuspendUpdates() which freezes drawing when multiple changes to data are made. This is only required if you are doing lots of changes in one update.

Try that! It’s the best way to get highest performance out of SciChart. And don’t forget to enable DirectX ‘Visual Xccelerator Engine’ as in the Performance Tips and Tricks documentation!

Best regards
Andrew

  • You must to post comments
Showing 3 results
Your Answer

Please first to submit.