Xml Data Binding Performance Issue (Re-post)
I am running the .Net 3.0 RTM and have come across a performance problem with data binding. I am data binding XML which is updated once per second to a ListView/GridView . I have noticed that cpu utilization increases rapidly as the number of items in the ListView grows. For example, on my IBM Thinkpad 43p, if I have 3 items, cpu utilization is 8%. If I have 6 items cpu usage grows to 30%. At 12 items it is approaching 100%.As the number of lines of XML grows, the performance gets worse.In the code behind, I have a single XmlDocument that I set to the data context of the ListView:
XmlDocument
doc =newXmlDocument();instrumentList.DataContext = doc;
When new Xml is available, I do the following:
doc.LoadXml(theXml);
This approach causes serious perfromance issues. Unbinind and rebinding did not resolve this (not sure why). The other thing that did not work was using XmlDataProvider.DeferRefresh when updating the doc.
The only solution I found was to create a new XmlDocument on on every update, LoadXml and rebind on every update:
XmlDocument newDoc = new XmlDocument();
newDoc.LoadXml(theXml);
instrumentList.DataContext = newDoc;
This improves things but puts pressure on the garbage collector by creating a XmlDocument once per second. Are there any other solutions? Object binding has better performance but is notconvenientfor our design.
Steve
-
Here is my XAML, XML:
<
Grid><
Grid.RowDefinitions><
RowDefinitionHeight="50"/><
RowDefinition/></
Grid.RowDefinitions><
ButtonGrid.Column="0"Grid.Row="0"Click="OnClick"Background="LightGreen">Update Me!</Button><
ListViewItemsSource="{Binding XPath=Instruments/Instrument}"SelectionMode="Single"Background="Aqua"Name="instrumentList"HorizontalContentAlignment="Center"Grid.Column="0"Grid.Row="1"><!--
<ListView ItemsSource="{Binding Source={StaticResource Instruments},XPath=Instruments/Instrument}"
SelectionMode="Single" Background="Aqua"
Name="instrumentList" HorizontalContentAlignment="Center" Grid.Column="0" Grid.Row="1">
--><
ListView.View><
GridViewx:Name="myGridView"><
GridViewColumnHeader="Instrument Type"DisplayMemberBinding="{Binding XPath=@Type}"Width="120" /><
GridViewColumnHeader="Serial Number"DisplayMemberBinding="{Binding XPath=@SerialNumber}"Width="100"/><
GridViewColumnHeader="Address"DisplayMemberBinding="{Binding XPath=@Address}"Width="80" /><
GridViewColumnHeader="State"DisplayMemberBinding="{Binding XPath=@State}"Width="80"/></
GridView></
ListView.View></
ListView></
Grid><
Instruments><
InstrumentType="ACQ-TUV"SerialNumber="12345"Address="192.168.0.2"State="Idle"/><
InstrumentType="ACQ-SM"SerialNumber="01234"Address="192.168.0.4"State="Idle"/><
InstrumentType="ACQ-ELSD"SerialNumber="23456"Address="192.168.0.8"State="Idle"/></
Instruments>Steve
[12236 byte] By [
SB] at [2007-12-27]
try setting Binding Mode to '
OneWay' and see if that helps
Lee,
Thanks for the suggestion.
Setting the binding to OneWay had no effect on performance.
Here are my numbers on a T43p, 23 lines of XML:
Creating a new XmlDocument and rebinding 10% CPU
Single XmlDocument : Unbinding, LoadXml and Re-Binding in code 40% CPU
Single XmlDocument : LoadXml 100% CPU
I got the same results with One Way and Two Way binding.
Steve
SB at 2007-9-4 >

You say your performance degrades as the "lines of XML" increases. I assume this to mean that the more <Instrument> elements you have the longer it takes. Is that correct? If so my guess would be that it's because you haven't set the ListView to use virtualization and so all the UI elements are being created to represent every Instrument in the set.
Set the attached property VirtualizingStackPanel.IsVirtualizing="True" on your ListView and see if that helps.
HTH,
Drew
I think the default value is true unless when there are groups in listview. my guess would be to somehow disconnect bindings, populate the document and bind again, would help
I can see the virtualizing behavior. I think that visrtualizing is the default. However, the performance is already an issue with only 6 items! As I stated in an earlier post, unbinding, updating the doc and re-binding is better but still has an unacceptable 40% cpu utilization.
Steve
SB at 2007-9-4 >

Oh, duh, I guess I didn't realize it virtualizes by default.
Out of curiousity, have you tried using an XmlDataProvider instead of setting the XmlDocument directly? That way you could leave the binding in place ('cause it'll be bound to the XmlDataProvider), load the new XmlDocument on a background thread and then, when loading of the document instance completes, assign the newly loaded instance to the Document property of the XmlDataProvider which should automatically cause the UI to update. Also, if you set the IsAsynchronus property to true on the XmlDataProvider you should gain a little more responsiveness because it will generate the node collection that will be bound to on a background thread before actually binding to the UI.
That said, this will only help with the performance of dealing with the XML itself. While that should not be overlooked, if the UI element creation really is the thing dragging you down here, then I don't know how much this will really help.
The thing is your ListView is so simple I don't know why it would be slow. Is this really the exact UI you are testing with? Out of curiousity, have you tried binding your data to something other than a ListView/GridView? Like say... a simple ListBox for starters... just to see if the perf issues have anything to do with that specific UI setup you have. If you have the same perf issues with a simple listbox writing out a single value, then it's safe to say it's a databinding performance issue rather than a UI performance issue. I'd even go a step further and figure out what your baseline performance is like when you're not even bound to a UI element. What's it costing you to XmlDocument and execute your XPath statement?
HTH,
Drew
one more thought, when the data is available can you try doing this
instrumentList.DataContext = null;
doc.loadXML(...);
instrumentList.DataContext = doc;
I tried that in the past and there is no improvement in performance unless I call GC.Collect() which not recommended. There is a some improvement (from 100% cpu to 40% cpu) in performance if I null out the individual bindings, LoadXml and rebind. 40% is still to high.
Steve
SB at 2007-9-4 >

Hi Drew,
The same problems exists with the XmlDataProvider. The IsAsynchoronous has no effect on performance.
If I do the following I get poor performance:
xmlProvider.Document.LoadXml(theXml);
I get good performance if I do the following:
XmlDocument newDoc = new XmlDocument();
newDoc.LoadXml(theXml);
xmlProvider.Document - newDoc;
However, I am concerned about having to new and garbage collect the newDoc once per second.
The ListView in post is the exact one I am working with. the problem exists in every ListView I have created.
I have not tried a list box. Data binding a single value has good performance. It is when I get past 10 values that perfromance degrades.
Base perfromance when I am not DataBinding and just working with the XML is less than 1% cpu.
Steve
SB at 2007-9-4 >

You shouldn't be too worried about new-ing the XmlDocument instance every second. You have to realize that even if you keep the XmlDocument instance around and use LoadXml you are freeing and creating an entire object graph of XmlNodes under the covers. It's an internal implementation detail of the XmlDocument, but it still exists and there's no way to get around that. Uber-fast allocations and GC is what the CLR is designed for. Consider something like ASP.NET where every request to a web form is creating and freeing giant object graphs made up of hundreds of controls. Unless you do some memory profiling and find it to be a problem, I wouldn't worry about it too much.
At this point though, your biggest hurdle seems to be WPF's performance and I'm still curious as to why it would 'cause you such a headache with such a simple scenario. I'm going to try and take your code and put together a sample app to see if I can help figure out the problem. I'll try and do this now on my lunch break and get back to you...
Cheers,
Drew
Thanks Drew,
I also tried XmlDataProvider.DeferRefresh but it did not seems to do anything.
Steve
SB at 2007-9-4 >

Alright, here's what I did:
- Took your ListView definition.
- Hooked it to XmlDataProvider which initially contains no data (because I load it dynamically) and has the XPath set to get the Instruments.
- Changed the implementation of the Update button to kick off a DispatcherTimer to load the data every second which looks like this:
this
.updateButton.Click += delegate
{
new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate { this.DoBind(); }, this.Dispatcher);
}; - Implemented the DoBind method like so (hacked the ticks in just so I could see it changing):
XmlDocument
document = new XmlDocument();document.LoadXml(@"<Instruments>
<Instrument Type=""ACQ-TUV"" SerialNumber=""12345"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-SM"" SerialNumber=""01234"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-ELSD"" SerialNumber=""23456"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
</Instruments>");
XmlDataProvider provider = (XmlDataProvider)this.grid.Resources["testData"];
provider.Document = document;
No matter how many <Instrument> elements I put into the sample data (I got up to 100 in my tests) the CPU usage for the app was between 3 and 5%. Even if the window was maximized showing 51 items in the list at once it didn't seem to exceed 15%. Also didn't matter if I was at the top of the list, in the middle or at the bottom. Performance was the same across the board.
Machine specs are: 3.8Ghz P4HT, 2GB of RAM, nVidia GeForce 6800 Ultra Go with WDDM drivers running on Vista RC2. Sooo.... I don't know what it could be causing you these problems on your end, but it doesn't seem to be WPF. 
Cheers,
Drew
Drew,
I can get the get the same performance without XmlDataProvider (see initial post):
-
The only solution I found was to create a new XmlDocument on on every update, LoadXml and rebind on every update:
XmlDocument newDoc = new XmlDocument();
newDoc.LoadXml(theXml);
instrumentList.DataContext = newDoc;
This improves things but puts pressure on the garbage collector by creating a XmlDocument once per second. Are there any other solutions? Object binding has better performance but is not convenient for our design.
--
To get the bad performance, change your code to the following:
XmlDataProvider provider = (XmlDataProvider)this.grid.Resources["testData"];
provider.Document.LoadXml(@"<Instruments>
<Instrument Type=""ACQ-TUV"" SerialNumber=""12345"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-SM"" SerialNumber=""01234"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-ELSD"" SerialNumber=""23456"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
</Instruments>");
Additionally, making the XmlDocument a class variable, nulling the binding, updating the document and rebind still has poor performance because WPF keeps a reference to the documents events and relies on GC to clean them up.
XmlDocument document = new XmlDocument(); //Class Variable
XmlDataProvider provider = (XmlDataProvider)this.grid.Resources["testData"];
provider.Document = null;
//you need to call GC.Collect here or you get bad performance.
document.LoadXml(@"<Instruments>
<Instrument Type=""ACQ-TUV"" SerialNumber=""12345"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-SM"" SerialNumber=""01234"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
<Instrument Type=""ACQ-ELSD"" SerialNumber=""23456"" Address=""" + DateTime.Now.Ticks + @""" State=""Idle""/>
</Instruments>");
provider.Document = document;
Steve
SB at 2007-9-4 >

Something tells me you're on a pre-mature optimization hunt... 
As I said in my previous post, your fears about GC'ing the XmlDocument don't seem to make any sense. LoadXml is causing far more things to GC'd than that single instance of XmlDocument. In your initial post all you said is it "improves things", but that didn't tell me you're not having 100% CPU usage @ 12 items any more... sounded more like maybe you were getting 80% CPU which was still bad. What CPU usage are you actually seeing with the XmlDocument instancing approach? Same as me?
What makes you think you'll have "bad performance" if you don't force a GC manually? What aspect of performance do you think will be bad exactly? Memory usage going up? More gen2 GC cycles eventually? Calling GC.Collect is actually one of the worst things you can do in an app since the GC is self-tuning and often has a better idea of when to perform a collection than your app ever will.
My advice would be to stick with the XmlDocument instancing approach (either setting the DataContext directly or through the XmlDataProvider) until you hook up a memory profiler and find that there's a real problem with the GC. I personally doubt you'll find that's an issue at all.
Good luck,
Drew