Archive

Posts Tagged ‘XmlDataProvider’

Optimizing a WPF Screen – Template Triggers

March 21, 2010 Leave a comment

This is the third post in a series about my experience optimizing a WPF screen.  Last time, I determined that the data binding to XML via an XmlDataProvider was a huge contributor to the performance issues.

For the final pass at optimizing my screen, I dug into the actual XAML templates.  As I said before, my expectations going into this project was that the actual cause of the performance issues was going to be the XAML – nested Grids, extraneous markup, etc.  I was very surprised to find that so much of the problem was stemming from the use of XML Binding.  Still, I had hopes that optimizations the XAML itself could yield some benefit.

The first thing I found were constructs of the form:

<Grid>
 <Rectangle Style="{StaticResource DatapointBubbleStyle}" />
 <Grid Margin="5">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"/>
   <RowDefinition MinHeight="25"/>
  </Grid.RowDefinitions>
  <TextBlock Text="{Binding XPath=@Label}" Grid.Row="0" />
  <Control Grid.Row="1" DataContext="{Binding XPath=Value}" />
 </Grid>
</Grid>

which I refactored to be like:

<Border Style="{StaticResource DatapointBubbleStyle}" />
 <Grid Margin="5">
  <Grid.RowDefinitions>
   <RowDefinition Height="Auto"/>
   <RowDefinition MinHeight="25"/>
  </Grid.RowDefinitions>
  <TextBlock Text="{Binding XPath=@Label}" Grid.Row="0" />
  <Control Grid.Row="1" DataContext="{Binding XPath=Value}" />
 </Grid>
</Border>

thus removing one nested Grid and making the layout a little more explicit (it took me a minute to realize that the Rectangle would be stretched to fill the entire containing Grid, thus simulating a border).  While this did yield a performance improvement, it was very small, only about 1% – certainly worth being aware of, but probably not even worth the time to hunt out and fix all such uses.

The second thing I found were constructs of the form:

<DataTemplate x:Key="DataNodeTemplate">
 <Grid>
  <TextBlock x:Name="TypeText" Text="{Binding XPath=@Type}"
    Visibility="Collapsed" />
  <Control x:Name="TemplatedDataPoint" Tag="{Binding XPath=@DisplayPath}"
    Loaded="Control_Loaded"/>
 </Grid>
 <DataTemplate.Triggers>
  <Trigger SourceName="TypeText" Property="Text" Value="field">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource FieldTemplate}"/>
  </Trigger>
  <Trigger SourceName="TypeText" Property="Text" Value="link">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource LinkTemplate}"/>
  </Trigger>
  <Trigger SourceName="TypeText" Property="Text" Value="text">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource TextTemplate}"/>
  </Trigger>
 </DataTemplate.Triggers>               
</DataTemplate>

This was a little bit trickier to understand.  The TextBlock was being used to pull some piece of information out of the XML which could then be fed into triggers to set the Template.  The Control_Loaded event was used to bridge from the leaf of the Layout tree (the DataNode) to the actual data (the DataPoint).  Refactoring this XAML to use DataTriggers would let me drop the containing Grid and the TextBlock, so I changed it to be this:

<DataTemplate x:Key="DataNodeTemplate">
 <Control x:Name="TemplatedDataPoint" Tag="{Binding XPath=@DisplayPath}"
    Loaded="Control_Loaded"/>
 <DataTemplate.Triggers>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="field">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource FieldTemplate}"/>
  </DataTrigger>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="link">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource LinkTemplate}"/>
  </DataTrigger>
  <DataTrigger Binding="{Binding XPath=@Type}" Value="text">
   <Setter TargetName="TemplatedDataPoint" Property="Template"
     Value="{StaticResource TextTemplate}"/>
  </DataTrigger>
 </DataTemplate.Triggers>
</DataTemplate>

What I found when I tested this was, if anything, a degradation in performance.  This was perplexing to me – I had reduced the amount of XAML in the screen, and yet things got less performant.

My previous experience with XML Binding suggested the answer.  The original XAML had one binding into the XML, with three Triggers against the result of that binding.  My revised XAML had three bindings into the XML. 

It is always tempting for me to think of triggers in a template to be analogous to a switch statement.  In truth, they are actually analogous to a series of unrelated if statements.  Presumably, WPF has some smarts to figure out when it actually needs to reevaluate any given trigger, but when the underlying data changes, all of the affected triggers are evaluated.

My refactoring of the XAML has exchanged one XPath lookup for three, and the cost of that has more than offset any savings from reducing the complexity of the XAML. 

Since in this particular place, the trigger conditions would never change once the screen was loaded, I returned to my object model for the Layout.  I changed the DataNode class to derive from Control, loaded the three ControlTemplates one time on startup, and added this code to the constructor:

switch (Type)
{
 case "field":
  Template = FieldTemplate;
  break;
 case "type":
  Template = TypeTemplate;
  break;
 case "text":
  Template = TextTemplate;
  break;
}

Testing this change revealed a measurable performance boost.  Investigating further, I found that the the FieldTemplate (the only branch being utilized in my test) had 35 triggers – all of them MultiDataTriggers – to determine things like exactly which control to consume (DateEdit, MaskedText, etc.) and to set various run-time states (“required”, read-only, etc.). 

In some cases, the triggers were nominally redundant:  there were 4 triggers to determine if the field was “required” – necessary because of the different permutations of data internal to the DataPoint.  Many others were for conditions that would never change once the record was displayed (e.g., the actual control type to be used based on the data type of the field).  But, even assuming that WPF is very efficient in determining which triggers need to be evaluated on a change of data, there were always many extraneous triggers that would have to be evaluated.

As a quick test, I removed the 30 or so trigger conditions that would never pass for my test scenario.  This resulted in about a 20% performance increase over my previous results.  I returned to my data model and refactored the DataPoint class to calculate all of the trigger conditions intelligently in code and expose the results as DependencyProperties.  I refactored the FieldTemplate to bind to these results, and removed all of the triggers from my templates.

Using the object-model binding and refactored templates, I reran my tests and collected new metrics:

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

Which is a 25% improvement over my last time of 449ms, and 75% improvement over my initial time of 1390ms to load the screen.  The apparently improvement in time to set a DataNode value is sufficiently small that it may be the result of random noise in the process (although the possibility of a 15% improvement there is not bad news).

My take away from all of this is that declarative logic in the XAML via triggers is something to be considered carefully – particularly for things that are relatively static. 

This makes me think that I should take another look at the Visual State Manager.  When I first encountered this, several years ago, I dismissed it as a hack for Silverlight that was back-ported into WPF to appease the “cross platform developer” crowd.  Since I was strictly a WPF developer, and everything you could do with VSM you could do with triggers, I didn’t give it any further thought.  Now, though, I’m not so sure.  Most of the triggers that I see in XAML are to transition between states – and if the logic for determining this is complex, this logic can be much more efficiently be performed in code.  Maybe there is some gold in there, after all.

Since last time, I’ve done some reading on MVVM, and I’m pretty much sold on it.  It just fits with the way that I think.  It takes the WPF concept of a “lookless control” and expands it to a “lookless data model”.  My previous exposure to MVVM was through some projects on CodePlex that sought to build a sophisticated framework for MVVM (like Cinch).  These had led me to believe that MVVM was complex and deterred me from serious consideration at the time.  I went back to the WPF page on CodePlex and found the MVVM toolkit on the WPF Futures page, which gave me a better appreciation for the simplicity and elegance.  I strongly recommend the pattern.

In addition to Cinch, I’m also going to be taking a deeper look at MVVM Foundation and MVVM Light Toolkit.  I’ll write about my findings in the future.

Optimizing a WPF Screen – XmlDataProvider

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.

Optimizing a WPF Screen – The Problem

The new version of my company’s flagship application was in trouble – the project floundering and was in danger of being cancelled.  It seems that the primary screen of the application was ponderously slow – slow to come up, and vaguely clunky to use – and was eating up an excessive amount of memory.  I was asked to crack it open and see if there was anything to be done.

A single run of the application demonstrated that there were definitely things amiss.  It took almost 2 full seconds to load and render the screen, and memory consumption would jump by at least 50mb.  While this might make sense if the screen were fetching and displaying an enormous amount of data, this wasn’t the case – the screen only displayed about 60 fields worth of data.  Furthermore, if the screen were repeatedly opened and closed, memory consumption would continue to rise – easily growing to several hundred megabytes.

I knew, in broad strokes, how the screen worked.  A call was made to a WCF Service which retrieved the data from the DB, formatted it in a way that was more easily accessible by the client layer, and returned an XML document.  The screen would then render this data.

I had been involved in the data layer of the application, and was intimately familiar with that part of the process.  I was pretty confident that the fetching and generation of the data wasn’t the problem, but I wanted to rule that out first.  A simple test application demonstrated that the data was returned from the WCF call in under 300ms.  That left the UI layer, so I turned my attention there.

The application’s UI is written using WPF and makes extensive use of DataTemplates to render the UI directly off of the data-model, which is held in a single XmlDataProvider.  There is also a automation engine which enables customer configured validation logic – for instance, if field 1 has the value X, flag fields 2 and 3 as “required” and hide field 4.

I captured the XML from the WCF call and built a sandbox application that contained only the primary screen of the application.  With that, I captured some baseline metrics:

Load the screen 1390ms
Set a value in the XML which triggers automation 650ms
Set a value in the XML which doesn’t trigger automation 30ms
Memory consumption (via GC.GetTotalMemory(true)) 82mb

None of that was good news.  Even the 30ms time was alarming.  I realize that 30ms isn’t very much, but that was being incurred with every keystroke via the UI, and that was on my hefty development box.  Just simply typing in a field would put things under load.

So, I had determined quantitatively that there was an actual problem, and that we were not just battling user-perception.  And, I had some baseline numbers to use to verify if a change had a positive or negative effect.

Next time, I’ll drill into the actual XAML and see what there was to be found there.

Follow

Get every new post delivered to your Inbox.