WPF performance

Jul 16, 2013 at 6:38 PM
I would like to use OxyPlot in a WPF application, but appear to be encountering a performance issue. I have 8 plots in a scroll viewer, ~4 onscreen at a time. The plots are initialized and configured in code. They are updated in real-time with data (maybe 10 points per second each) up to a defined maximum amount. If I swap the type from WPF to WindowsForms using a hosted container, the performance doubles. Let me rephrase: WPF has 100% CPU usage and low UI response, WF has 50% CPU usage and good UI response.

Any idea why the difference in CPU usage/performance? Is there something I'm missing to get better WPF usage? I'm not using bindings; the simple code that works for WF is also used for WPF.
Jul 17, 2013 at 7:23 PM
I've noticed the same thing. I am displaying different charts in WPF with OxyPlot, and the performance is not great (i.e. panning, zooming, and tracking). Some of the charts have as few as a dozen or two points and aren't updated dynamically. The WinForms example and the online Silverlight example work perfectly.
Jul 18, 2013 at 4:44 PM
I think it's a problem with WPF itself.
Jul 18, 2013 at 4:51 PM
It might be, but my test tries to use the objects using code rather than XAML/binding. The test application itself is WPF. The difference I'm noticing is in using the WPF version of OxyPlot vs the Windows Forms version hosted in the WPF application.
Coordinator
Jul 19, 2013 at 7:32 AM
Can you profile your application to see if the time is spent in your application, the OxyPlot library or in the WPF core?
I have also noticed that other platforms are performing much better than WPF. It would be interesting to know if there is something that can be improved in the WPF implementation!
Jul 19, 2013 at 1:58 PM
I have since gone ahead using a hosted WF OxyPlot. Seems I'll need a wrapper around it so that resizing will work properly.

I'll try putting together a test app once I have time and have a better idea how to use SharpDevelop's profiler; couldn't find a better one.
Jul 23, 2013 at 7:38 PM
Below code is currently set to use OxyPlot.WindowsForms control in a WPF application. Change the XAML to use OxyPlot.WPF for comparison. I was unable to retrieve any insight using either SharpDevelop or SlimTune profilers. Perhaps someone else can make sense of the data.

XAML:
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
        xmlns:oxy2="clr-namespace:OxyPlot.WindowsForms;assembly=OxyPlot.WindowsForms"
        Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" WindowState="Maximized">
  <!-- Comment/Uncomment the plot variant to use: WPF/WF -->
  <!--<oxy:Plot x:Name="MyPlot" />-->
  <WindowsFormsHost>
    <oxy2:Plot x:Name="MyPlot" />
  </WindowsFormsHost>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1 {
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window {
    public MainWindow() {
      InitializeComponent();
      InitPlot();
      // initialize plot data points
      for(int i = 0; i < TOTAL_AXES; i++) {
        _SeriesIndex[i] = 0;
        sbyte _DataValue = 0;
        for(int j = 0; j < TOTAL_POINTS/2; j++) {
          _SeriesData[i].Add(new OxyPlot.DataPoint(_SeriesData[i].Count, _DataValue));
          _SeriesData[i].Add(new OxyPlot.DataPoint(_SeriesData[i].Count, -_DataValue));
          _DataValue++;
        }
      }
      // set up refresh timer for ~30fps
      _PlotRefresh.Interval = new TimeSpan(0, 0, 0, 0, 33);
      _PlotRefresh.Tick += new EventHandler(Timer_Tick);
    }

    private void InitPlot() {
      OxyPlot.Series.LineSeries srs = null;
      OxyPlot.Axes.LinearAxis axisBottom =
        new OxyPlot.Axes.LinearAxis(OxyPlot.Axes.AxisPosition.Bottom);
      OxyPlot.Axes.LinearAxis axisLeft = null;
      double wfpos = 1.0;

      MyPlot.Model = new OxyPlot.PlotModel();
      MyPlot.Model.Padding = new OxyPlot.OxyThickness(0);
      // single bottom axis
      axisBottom.MajorGridlineStyle = OxyPlot.LineStyle.Solid;
      axisBottom.MinorGridlineStyle = OxyPlot.LineStyle.Solid;
      axisBottom.MinimumPadding = 0;
      axisBottom.MaximumPadding = 0;
      MyPlot.Model.Axes.Add(axisBottom);

      for(int i = 0; i < TOTAL_AXES; i++) {
        // initialize each left axis
        axisLeft = new OxyPlot.Axes.LinearAxis();
        axisLeft.Position = OxyPlot.Axes.AxisPosition.Left;
        axisLeft.MajorGridlineStyle = OxyPlot.LineStyle.Solid;
        axisLeft.MinorGridlineStyle = OxyPlot.LineStyle.Solid;
        axisLeft.Minimum = -128;
        axisLeft.Maximum = 127;
        axisLeft.MinimumPadding = 0;
        axisLeft.MaximumPadding = 0;
        axisLeft.EndPosition = wfpos;
        wfpos -= ((1.0 / TOTAL_AXES) - 0.01);
        axisLeft.StartPosition = wfpos;
        wfpos -= 0.01;
        axisLeft.Key = "AxisKey" + i.ToString();
        MyPlot.Model.Axes.Add(axisLeft);
        // initialize each series for the axis
        _SeriesData.Add(new List<OxyPlot.IDataPoint>(TOTAL_POINTS));
        srs = new OxyPlot.Series.LineSeries();
        srs.CanTrackerInterpolatePoints = false;
        srs.StrokeThickness = 1;
        srs.Points = _SeriesData[i];
        srs.YAxisKey = axisLeft.Key;
        MyPlot.Model.Series.Add(srs);
      }
    }
    private void Window_Loaded(object sender, RoutedEventArgs e) {
      _PlotRefresh.Start();
    }
    private void Timer_Tick(object sender, EventArgs e) {
      // this is meant for use with dynamic data/series, but the effects
      // are similar using static data for testing
      MyPlot.RefreshPlot(true);
    }

    private const int TOTAL_AXES = 4;
    private const int TOTAL_POINTS = 512;
    private List<IList<OxyPlot.IDataPoint>> _SeriesData =
      new List<IList<OxyPlot.IDataPoint>>(TOTAL_AXES);
    private int[] _SeriesIndex = new int[TOTAL_AXES];
    private System.Windows.Threading.DispatcherTimer _PlotRefresh =
      new System.Windows.Threading.DispatcherTimer();
  }
}
Jul 30, 2013 at 4:13 PM
Not sure how much help this is, but I have some results from the WPF Performance Suite (part of Windows SDK). It's not very detailed, but it indicates "Rendering Thread" uses 50% CPU time and "Layout" uses 20%. As far as I can tell, WPF/OxyPlot is using hardware acceleration. RenderingCapabilities.Tier returns 2, which is the highest value indicating support for hardware acceleration. The performance suite also indicates the entire OxyPlot control being frequently marked as "dirty" and requiring refreshing/rendering. This is probably expected behavior when issuing RefreshPlot(true).
Jul 30, 2013 at 6:57 PM
Edited Jul 30, 2013 at 7:01 PM
(Formatting is lacking, just copy the table into notepad.)
"Top 20" results from SharpDevelop profiler:
Name                                                       Call Count   Time Spent     TS (self)      TS (per call)   TS (self/call)
------------------------------------------------------------------------------------------------------------------------------------
WpfApplication1.App.Main                                       1      19487.779297ms   26.708881ms   19487.779297ms   26.708881ms                                     
OxyPlot.Wpf.Plot.ArrangeOverride                              22       6385.794584ms    0.526685ms     290.263390ms    0.023940ms                                     
OxyPlot.Wpf.Plot.UpdateModelAndVisuals                        21       6327.732324ms    3.068244ms     301.320587ms    0.146107ms                                     
OxyPlot.Wpf.Plot.UpdateVisuals                                21       5852.759366ms    3.663617ms     278.702827ms    0.174458ms                                     
OxyPlot.PlotModel.Render                                      21       4932.378369ms   14.397502ms     234.875160ms    0.685595ms                                     
WpfApplication1.MainWindow..ctor                               1       3128.988608ms   60.921899ms    3128.988608ms   60.921899ms                                     
OxyPlot.PlotModel.RenderAxes                                  42       2628.290695ms    0.843887ms      62.578350ms    0.020093ms                                     
OxyPlot.Axes.Axis.Render                                     210       2627.099862ms    1.284219ms      12.509999ms    0.006115ms                                     
OxyPlot.HorizontalAndVerticalAxisRenderer.Render             210       2625.815643ms   13.369001ms      12.503884ms    0.063662ms                                     
WpfApplication1.MainWindow.InitializeComponent                 1       2268.427159ms    1.322134ms    2268.427159ms    1.322134ms                                     
OxyPlot.HorizontalAndVerticalAxisRenderer.RenderMajorItems   105       1827.486131ms    4.793490ms      17.404630ms    0.045652ms                                     
OxyPlot.MathRenderingExtensions.DrawMathText                 798       1570.425264ms    1.443802ms       1.967951ms    0.001809ms                                     
OxyPlot.MathRenderingExtensions.DrawMathText                 798       1568.981462ms    4.374377ms       1.966142ms    0.005482ms                                     
OxyPlot.Wpf.ShapesRenderContext.DrawText                     798       1561.215147ms   11.262844ms       1.956410ms    0.014114ms                                     
OxyPlot.PlotModel.AdjustPlotMargins                           21       1155.307446ms   14.229514ms      55.014640ms    0.677596ms                                     
OxyPlot.PlotModel.MaxSizeOfPositionTier                       42       1120.887111ms    1.372298ms      26.687788ms    0.032674ms                                     
OxyPlot.Axes.Axis.Measure                                    105       1119.405653ms    4.064151ms      10.661006ms    0.038706ms                                     
OxyPlot.PlotModel.RenderSeries                                21       1091.402684ms    2.620846ms      51.971556ms    0.124802ms                                     
WpfApplication1.App..ctor                                      1       1088.683889ms  161.166694ms    1088.683889ms  161.166694ms                                     
OxyPlot.Series.LineSeries.Render                              84       1087.583511ms   76.632450ms      12.947423ms    0.912291ms                                     
I also have the SharpDevelop profile project file if needed (~20MB).