Quantcast
Channel: Mullin @ Work » Optimization
Viewing all articles
Browse latest Browse all 2

Optimizing a WPF Screen – XmlDataProvider

$
0
0

This is the second post in a series about my experience optimizing a WPF screen.  Last time, I built a test environment, established that there was, in fact, a problem, and gathered some baseline metrics.

After a little spelunking in the code, I was able to determine that, at its heart, the screen was deceptively simple:

<ScrollViewer VerticalScrollBarVisibility="Auto">
  <ItemsControl ItemsSource="{Binding XPath=//DataRegions/DataRegion}"
                VirtualizingStackPanel.IsVirtualizing="True"
                ItemTemplate="{StaticResource DataRegionTemplate}" />
</ScrollViewer>

Now, I knew that the XML being rendered contained two sections – one contained the data, and the other contained information about how the data should be laid out.  This ItemsControl was binding to the top of the layout tree – a DataRegion contained rows, which contained columns, which contained groups, which themselves contained rows, which contained columns, which contained DataNodes.

An aside about this construct: the purpose here was to allow our customers to lay out a data-entry form tailored to their specific needs.  We explored having the screen builder generate XAML, and either compile it or parse it at runtime.  However, we learned (much to our surprise) that it was actually faster to have an ItemsControl (or similar container) with an appropriate (or even dynamic) DataTemplate than it was to have a larger piece of explicit XAML.

A quick search of the code told me that DataRegionTemplate was a DataTemplate that contained an ItemsControl bound to RegionRows, and that the ItemTemplate of this control pointed to another DataTemplate containing an ItemsControl, and so on down to the DataNode.  The layout that was being rendered was not overly complex, so I didn’t think there was anything particularly wrong with this scheme.  One thing that did draw my attention, however, was that all of the DataTemplates employed XPath binding.

Full discloser: I am not a believer in XML as a data-model.  I think that there are lots of places where XML is the perfect tool – persisting dynamic semi-structured data, as a medium for communication between distributed systems, that sort of thing.  But, unless you use an accompanying schema (which I pretty much never see), it is not typed and there is no way to discover what the structure could or should be, only what it is.  It just feels sloppy – like an old VB program where you’d let all variables be VARIANT.  You could do it, but it almost always led to problems.  So, I was suspicious of the fact that the data was accessed and manipulated entirely within an XML document.

With this bias in mind, my first thought was to render the XML into an object model, and bind to that object model.  Since the layout portion of the screen was relatively simple – only 7 different classes, each with just a few attributes and a list of child elements – I decided to start there.

An hour’s work later, and I had my 7 classes and their associated revised DataTemplates, and had read the layout data out of the XML and built an object tree.  I fired up my test application and gathered new metrics.  All of the numbers dropped by 20%.

I’ll admit, at this point, I was a little surprised.  Despite my bias against XML, I hadn’t really expected much from this change.  I thought I’d see some improvements, but I had assumed that the real optimization work would happen in the ControlTemplates and DataTemplates further down – like on the DataNodes themselves.

The obvious next step was to render the DataNode portion of the XML into an object model.  A brief investigation, however, revealed that this would be much more involved than I’d like – especially just to experiment with the idea.  While the data portion was just a simple list of keyed DataNodes, there were dozens of different types of DataNodes, each with its own DataTemplate in the XAML – some of which were rather complex.  I wasn’t going to revise all of these DataTemplates just to do my test.  Even if I were to limit my efforts to the DataTemplates that were actually used by my test data, it was too many.

So, instead, I went back to my source XML and revised it so that all of the DataNodes were of the same type.  With that, I reran my initial tests to get a new set of baseline metrics.  Now it was a relatively simple matter to read the XML into a list of my one type of DataNode, and revise the much smaller number of DataTemplates to bind to my object model rather than to the XML.

I reran my tests and collected some new metrics:

Load the screen 449ms
Set a value in a DataNode which triggers automation 6ms
Set a value in a DataNode which doesn’t trigger automation 1ms
Memory consumption (via GC.GetTotalMemory(true)) 15mb

Which is a 70%(ish) reduction in the initial load time and memory consumption, and a 99% reduction in the data-updating steps.  With this one simple (although involved to fully implement) change, the application had moved from being “too horrible to use” to “eh, it’s a little slow” – and I hadn’t even started to look at the XAML itself.

Now that I knew that there was hope, I went back to try to understand why this change had made such a big difference.  I started by seeking an answer to the simplest question I could raise from the initial data: why did it take 30ms to set a value in the XML and let that propagate through UI?  All that I was doing was changing the InnerText of a single Xml element which resulted in the contents of a single TextBox being changed – why did that take any time?

So, I fired up Reflector (if I had to pick one tool that was most useful for investigating things in .Net, it would be Reflector) and poked around in XmlDataProvider, XmlDocument, and the XPath-based binding constructs.

It’s a little murky in there, and without the ability to step into the WPF code, its a little difficult to tell exactly what is happening.  But, what I concluded was this: ultimately, the binding was attaching to the various NodeChanging events exposed by the XmlDocument as a whole – the individual Xml nodes do not have any events.  This means that, when the contents of the Xml changes, all of the XPath binding objects would receive a notification, and they would look at the information in the notification to determine if this was about a node that they cared about.  Only then would the correct XPath binding object fire a change to the UI.

With all of the bindings that actually occurred the screen, there were literally hundreds of binding objects that had to get notified, even though only one of the cared.  Even though they were individually fast, this added up to enough time to notice.

When I moved to binding to an object model, I was able to use individual DependencyProperties, which fired much more precise change notifications.  So, when I changed a value in the object model, only the individual bindings that cared had to get notified.

Its possible that, at some point in the future, the .Net Framework will be changed so that individual XmlNode objects expose their own notifications, and that this change would enable the original binding scheme to work.  However, even if that were the case, I would still expect it to perform less well because I just can’t believe that evaluating the XPath is cheap – and there are certainly many events that would require it to have to evaluate the XPath again.  That’s just speculation, of course.

My take away from this is that, while there are circumstances where using the XmlDataProvider is appropriate, in larger use cases, you’re better off ditching it.

This brought to mind the MVVM pattern (which I am only now starting to really delve in to).  My (admittedly limited) understanding of the MVVM pattern tells me that what I just did in my application is introduce a ViewModel layer – another layer just below the UI that translates the DataModel into a form more appropriate for the UI layer’s consumption.  It also handles the UI-level logic, so that the UI itself can be as dumb as absolutely possible.  I think I’m going to have to do some deeper research into that pattern – now that I understand a little better the “why” of it (or, at least a “why” of it), I think it will make better sense to me.

Next time, I’ll crack open the XAML itself, and see if any further optimizations can be made at that level.



Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images