.Net 2.0 TreeView Performance Problem
No doubt some of you may have noticed some pretty poor performance in NCoverExplorer when running it under .Net 2.0. I've made some tweaks recently for the imminent NCoverExplorer 1.3.5 release which should improve things, but it still sucks big time in performance compared to .Net 1.1.
I thought that all things .Net 2.0 were supposed to be brighter, shinier and faster I hear you say? Anyone who has had the misfortune to use the dog turd that is the VS.Net 2005 IDE will happily put that "generalisation" to rest (I'm saving that rant for another post). It appears that sadly the same "Microsoft Improvement Programme" has been applied to the TreeView control and it's giving me the heebies.
The problem? I need to update the .Text of the nodes in the tree dynamically. I do this on a lazy loaded basis (mostly for speed reasons that worked under .Net 1.1). As many NCoverExplorer users will know, I offer a variety of "views" such as %, #unvisited nodes, function visits etc - all of which require updating the .Text property of the nodes as you expand them.
In .Net 2.0, Microsoft changed the internal implementation of the TreeView control - but not for the better for my requirements.
In .Net 1.1, calling .BeginUpdate/.EndUpdate around your updates to .Text did absolutely nothing in terms of performance gains. In .Net 2.0 however, you absolutely MUST wrap those same updates to .Text in this call, or else your performance really goes to hell and back (under the hood it generates literally millions of windows messages for a 10MB coverage file loaded). Sure enough this does improve performance considerably - but still nowhere near .Net 1.1 levels as you can see below.
You can download a little test app I knocked up from here. The results of loading around 15,000 nodes in a 1 x 25 x 25 x 25 hierarchy and expanding a node to see it's 25 children are shown. It's a standard TreeView with an override to OnBeforeExpand, which simply loops through the immediate child nodes only and updates the .Text with/without a BeginUpdate/EndUpdate wrapper.
Under .Net 1.1 (time to expand a node):
0.2 secs (using .BeginUpdate/.EndUpdate and updating .Text of the children)
0.2 secs (just updating .Text with no .BeginUpdate/.EndUpdate wrapper)
0.007 secs (just calling .BeginUpdate/.EndUpdate and not updating anything)
Looks pretty good right? Now the bad news:
Under .Net 2.0
1.1 secs (using .BeginUpdate/.EndUpdate and updating .Text of the children)
30.6 secs (just updating .Text with no .BeginUpdate/.EndUpdate wrapper)
1.1 secs (just calling .BeginUpdate/.EndUpdate and not updating anything)
So using .BeginUpdate/.EndUpdate (as you are now forced into under .Net 2.0 as you can see by the nightmare time of not using it) it is still around six times slower using .Net 2.0. That's just crap.
Interestingly with the third of those statistics above you can see that the problem is the .EndUpdate call, which in .Net 2.0 now does a whole lot of windows message blasting - regardless of whether there actually was anything updated as there wasn't in this case.
It is also obviously related to the size of the tree. Below is a graph showing the results with progressively 5, 10, 15, 20, 25, 30 and 35 child nodes under each node. Remember I am only updating the immediate child nodes, not every node in the tree!
It's pretty obvious that the .Net 1.1 implementation both performs and scales a heck of a lot better than .Net 2.0. If there is a reason for this garbage I'd love to hear it...
I'm open to suggestions from anyone else out there who has hit the same problem and figured out a solution. I started a thread on the Microsoft forums in desperation. I already lazy load so users should only incur the hit the first time they expand a node. However it's still pretty darn annoying to wait over a second every first node click to see just a couple of child nodes appear underneath!