The History of Improving the History Bin
During the sprint for Jing 1.2.7, one of my tasks was to add sorting and filtering to the History bin. While adding that functionality was trivial, it was immediately noticeable that it was painfully slow. It was so slow that the sorting in History was almost pointless. Up until that point, the History always had been sluggish, or at least seemed sluggish, and with sorting and filtering being so slow, something had to be done. [In]conveniently, profiling the History for performance gains was also one of my tasks for that sprint.
After about a week of profiling and testing, I found three major bottlenecks to the History’s performance. Unfortunately, they were all interrelated, non-trivial fixes. Two of the fixes were not completed until 1.3.
In order to understand the first, and arguably largest, bottleneck, you have to realize that the History is essentially this XAML snippet:

Yup, the History is basically nothing more than a WPF ListBox data-bound to a collection of objects that we use to encapsulate each capture. To achieve the tabular look, we overrode its ItemsPanelTemplate to use a WrapPanel. This gave us the benefit of having rows re-arrange themselves whenever the History window was resized. However, it also caused a huge hit in rendering performance.
By default, ListBoxes use what’s called a VirtualizingStackPanel as its ItemsPanelTemplate. A virtualized panel does lazy-loading of UI elements. Specifically, virtualized panels will only generate (realize) the UIs for elements that are actually visible on the screen, and destroying them when they become non-visible (unrealized). Since the History was using a WrapPanel, whenever the History was sorted, the UI for each capture was being destroyed and regenerated, regardless if it was on-screen or not. In other words, it wasn’t the actual sorting that was slow – it was the rendering of each capture’s UI that was.
Unfortunately as of this writing, Microsoft does not provide a VirtualizingWrapPanel, so if you want one, you will have to write one yourself. To warn you though, writing a custom virtualizing panel is time consuming and NOT trivial. Luckily, Dan Crevier has a series of blog posts on how to write one. Although I was working on an implementation, I was not able to finish it in time for the 1.3 release.
Another major bottleneck with the History was that it was a layered window. As I’ve discussed in other posts, layered windows in WPF are basically evil. Because of the per-pixel alpha blending, each pixel in the History was being rendered twice. The effect of this was if the History was sorted, each item was realized (because of the WrapPanel), and then rendered again. To prove this, I invalidated the client area of History by resizing and scrolling it, and saw its framerate drop to as low as 5 fps under Perforator. By making the History non-layered, resizing and scrolling became much more performant, with framerates on my Vista box getting as high as 60 fps. Sorting now was also improved since each capture was only being rendered once. However, there was still one bottleneck that made sorting noticeably sluggish.
Each capture had over 30 elements in its Visual Tree, according to Snoop. To confirm my suspicions, I ripped the History out into a test harness, invalidated the client area by sorting to force it to regenerate the UI for every capture. Using Perforator, the rate at which it was generating each UI was under 10. When I used DrawingVisuals to custom render each capture, the UI regeneration rate (Perforator calls this the Dirty Rectangle region rate) jumped to over 100! NOTE: I will discuss custom drawing in WPF and Visual Trees in more detail in a future post.
With those two fixes in place, the History has come a long way in terms of rendering performance and has been a major area in which the team has learned some best practices in WPF. As a FYI to other devs that may be getting started with WPF, I cannot stress these points enough:
- Avoid the tempation of using layered windows to make non-rectangular regions. Instead, use the GDI CreateRgn functions.
- Avoid complicated layouts. That is, be aware of the number of elements you use to compose your layouts, using Snoop and Perforator to make sure they do not adversely impact the UI generation rate. If you really need to, use custom drawing with DrawingVisuals to cut down on overhead.
- If you are displaying a collection of objects that could potentially become numerous, write a virtualizing panel to render only the objects that will actually be visible on the screen.
Now all I have to do is write a virtualizing WrapPanel to get some peace of mind..
Tags: drawing, layered-windows, performance, profiling, rendering, virtualizing panels, VisualDrawing, VisualTree, WPF
You can comment below, or link to this permanent URL from your own site.
February 6, 2008 at 8:55 pm
[...] may have noticed that loading times for the History bin can be really long. As discussed in this post, we’ve already implemented some techniques to improve this, except for one: implementing a [...]