Improving Scrolling Performance in WPF --Cedric Dussud

来源:互联网 发布:数据分析 培训课程 编辑:程序博客网 时间:2024/06/10 05:39

http://download.microsoft.com/download/2/d/b/2db72dcf-5ae0-445f-b709-7f34437d8b21/Scrolling_in_WPF.doc


Improving Scrolling Performance in Windows PresentationFoundation

Cedric Dussud
Microsoft Corporation

Summary: There are several ways to improve the scrolling performance oflarge data sets in Microsoft Windows Presentation Foundation (WPF). In order oftheir increasing performance, they are: using UI virtualization, recycling UIcontainers, and implementing a data-binding proxy object. (9 printed pages)

Introduction

This article tries to help answer the followingquestion: “I'm displaying many items in anItemsControl.How can I make scrolling/panning faster?”

Although Microsoft Windows PresentationFoundation (WPF) does its best to be as fast as possible, it is necessary, insome cases, to write some things yourself. This article represents a guide tohow to do that; it describes the performance bottlenecks that you'll see, andsuggests ways to get around them.

This article is divided into sections, eachof which briefly describes an implementation, along with its performance issues.Each subsequent implementation builds on the last and is faster, but requiresmore work on the part of the user (that is, you). The suggestions here apply equallywell to 1-D (StackPanel) and 2-Dlayouts.

There is also a sample application thatprovides an implementation of each performance optimization that is discussed(see the "Sample Application" section).

There are several ways to improve thescrolling performance of large data sets in Microsoft Windows PresentationFoundation (WPF). In order of their increasing performance, they are:

·        Using UI virtualization.

o  On by default in ListView and ListBox.

o  Trivial to enable in ComboBox.

o  Not implemented in TreeView. User can write a ListView that acts like aTreeView (see the "Resources"section for more info).

·        Recycling UI containers.

o  See the ContainerList class in the provided sample application for areference implementation.

·        Implementing a data-bindingproxy object.

o  See the ContainerListFastBinding class in the provided sample applicationfor a reference implementation.

Non-Virtualized UI

Because this article discusses a series ofmore performant ways to display items, it is best to start with the simplestimplementation as a reference point. This is mostly theoretical, because WPFsupports UI virtualization (see the next section) by default in ListBox and ListView.

Background

A panelin WPF is an element that hosts UI children and lays them out. That's a panel'sonly job; someone gives it a list of children, and it decides how to size andposition them properly. AStackPanel,for example, stacks children either from top to bottom or from left to right.

ItemsControls in WPF contain a set of items for display; typically, this is alist of data items that are specified by theItemsSource property. TheItemsControlis (effectively) responsible for creating visuals to display each data item andfor placing those visuals in a panel.

The simplest way for an ItemsControl to display children is tocreate one container for each data item and to give the entire set to a panel. Thepanel then has everything that it needs to handle the sizing, positioning, andscrolling of items. For example, by measuring all of the containers, it knowsthe scroll extent.

Implementation

Although WPF by default virtualizes most ofits ItemsControls, one could implementthis by creating anItemsControl thathas itsItemsPanel set to a StackPanel.

Performance Bottlenecks

The problem with the standardimplementation is that, for large data sets, the StackPanel will generate and lay-out containers that are likely off-screen.This is costly in two ways:

1.      The application willinstantiate containers that aren't visible, which wastes memory.

2.      Each layout pass computes thesize and position of all of the containers, which makes scrolling expensive.

Because both of these costs areproportional to the number of data items, this just doesn't scale for largedata sets. The best way to resolve this issue is with UI virtualization.

UI Virtualization

UI virtualization means deferring container generation until it is needed. Forexample, say that we have aListBox thatshows a list of 2,000 items in which only 20 are visible at a given time.ListView (technically, the VirtualizingStackPanel that is underneath)is able to generate containers the moment that they are scrolled into view. Thepanel also removes containers that no longer are needed, so that it will usearound 20 containers. For large data collections, the scrolling-performancebenefits are significant.

Suggested Implementation

Both ListBoxand ListView already implement UI virtualization.Because most large data sets in WPF are displayed inListBoxes orListViews,this is the effective starting point for most applications.

ComboBox has it off, by default, but it is fairly trivial to enable it. Justreplace its defaultItemsPanel withaVirtualizingStackPanel, and setthe panel'sIsVirtualizing propertytotrue.

TreeView does not implement UI virtualization. Currently, the bestworkaround is to create aListViewthat behaves like aTreeView (thatis, it knows how to add and remove children, and indent them properly). For agreat discussion ofTreeViewperformance issues and ways in which to solve them, see Beatriz Costa's blogpost:http://www.beacosta.com/blog/?p=42

Performance Bottlenecks

VirtualizingStackPanel in WPF creates items as they're scrolled into view, and cleans themup when they are moved off-screen. The problem is that expanding data templatesis very expensive; and, now that layout is doing far less work, a significantportion of the scrolling time is now spent creating containers.

Contribution (percent)

Operation

30

Creating containers (expanding data templates and item templates)

18

Measuring

15

Arranging

10

Removing old containers from the panel

10

Rendering containers

10

Data binding

6

Adding new containers to the panel

Table 1. Performance of ListBox

The most obvious way to improve the cost ofcontainer creation is to get rid of it wherever possible. This is known ascontainer recycling.

Recycling Containers

Containerrecycling is what it sounds like: Just reuse theoff-screen containers, instead of generating new ones. As of Microsoft .NET Framework,3.5, WPF does not implement container recycling in any of its panels.

Expected Performance Benefits

Although the performance benefits willdiffer substantially (depending on the complexity of the container, theimplementation of the recycling algorithm, the number of items that are data-boundinside the container, and so forth), implementing this should show asignificant improvement.

A good container-recycling algorithm shouldbe able to remove the cost of creating (30 percent), removing (10 percent), andadding (6 percent) containers to the panel. Removing 46 percent of thebottlenecks will almost double the scrolling speed.

Suggested Implementation

The simplest way to benefit from container recyclingis to write your own FrameworkElementthat knows how to scroll and display your data items.

The element can be responsible for:

·        Holding on to a list of data items(via a dependency property such as ItemsSource).

·        Instantiating containers, andassigning data items to them.

·        Creating its own UI:

o  Creating a panel to hold thecontainers.

o  Creating a scroll bar, asnecessary.

o  Creating exactly as manycontainers as will fit in the view.

Upon scrolling, the element will basicallyshift the data items to new containers, as appropriate. This way, the datamoves, but the containers do not. This entails:

·        Figuring out where each dataitem is now located in the viewport.

·        Updating each container topoint to the proper data item.

As before, the "look" of yourdata can still be defined by DataTemplates.If you use aContentPresenter (or ifthere is one in the visual tree of your container), it will continue to work.

For an example, see the ContainerList class in the sampleapplication.

Performance Bottlenecks

The performance bottlenecks for our sampleapplication now break down as follows:

Contribution (percent)

Operation

27

Arranging (16 percent is TextBlock.OnRender)

25

Data binding (BindingExpression.Activate)

20

Measuring (almost all TextBlock)

13

Rendering

Table 2. Performance of recycling containers

The main thing to note here is that the performanceis now tightly connected with the specific scenario. Most of the time now isspent laying out and renderingTextBlocks.The arranging, measuring, and rendering costs are necessary; this is good, inthat it means that we're starting to cut to the bone.

The fact that data binding takes up almost25 percent of the time is a result of our having six bindings per container. Onevery scroll, we're invalidating all of those bindings for each container, so thatthis shouldn't come as much of a surprise. If we still need to make scrollingfaster, getting rid of this cost is our next step.

Faster Bindings

Data binding is immensely useful and, whenused properly, fairly fast. Before taking the time to work around usingbindings, be sure to profile your application (see the "Resources"section) to ensure that it is indeed the bottleneck.

In this section, we'll assume that, on eachscroll, your application is reevaluating a series of bindings as described inthe preceding section. There are roughly two ways to get rid of this cost: Don'tuse data binding at all, or give the data-binding engine a little help.

Expected Performance Benefits

The expected benefits depend on whichimplementation you choose. It's possible to remove entirely the cost (25percent) of data binding by just not using the feature. You can also choose to "help"the data-binding engine, which will remove about half of the data-binding cost.

Suggested Implementation

RemovingBindings

Getting rid of data binding is a sure wayto get rid of that 25 percent bottleneck, but it isn't likely worth the cost;if you've chosen to use data binding, it is probably for a reason. You'll losetons of great features, such as the use ofDataTemplates,and you'll have to manage yourself all of the updates to data.

Because this implementation is highly application-dependentand fairly straightforward (although work-intensive), we won't discuss it furtherhere.

Helpingthe Data-Binding Engine

The problem with our sample application isthat, each time we scroll, we set theContentproperty on each container in the view to a new data item. This internallycauses theContentPresenter toupdate itsDataContext property,which means that all of its bindings are now invalid. The data-binding enginehere has no choice but to reactivate everyBindingExpression;that is, it needs to attach it to the common language runtime (CLR) property inquestion, and transfer the value to the targetDependencyProperty.

Both of these operations split the timeroughly equally. There's nothing that we can do about transferring the value;if it changes, that has to happen. We can, however, avoid attaching the bindingto the data object.

The trick here is always to keep the ContentPresenter pointed at the sameobject. To do so:

·        Create a proxy object that hasthe same properties as the data item.

o  It should hold on to aninstance of the data item, and forward calls to the data item to itsproperties.

o  It should implement INotifyPropertyChanged.

o  Modify your DataTemplates to reference the proxyobject, instead of the data item.

·        Have each container point to aproxy object, instead of a data item.

·        When scrolling, find eachcontainer's proxy object, and change its data item. If the proxy fires itsPropertyChanged event, the bindingswill be updated properly.

The ContainerListFastBinding.cs in the codesample shows how this can be done.

Performance Bottlenecks

As mentioned earlier, the bindingoptimization is expected to cut the cost of data binding in half for our targetscenario. The numbers now look like the following:

Contribution (percent)

Operation

34

Arranging (22 percent is TextBlock.OnRender)

26

Measuring (almost all TextBlock)

17

Rendering

11

Data binding (BindingExpression.TransferValue)

Table 3. Performance of recycling containers, and optimizing data binding

These are all mandatory operations. Althoughthey look like large bottlenecks, it means that this implementation is about isfast is it will get. The sample application literally only displaysTextBlocks; it should come as nosurprise that almost all of the time is spent preparing and rendering them.

There is also a savings that is notcaptured in this table. By avoiding changing theContent property, we're also avoiding a tree walk on each containerto update dependent children (because the property is inheritable). It's arelatively small cost, but it is still there.

Sample Application

The application consists of a ListBox with its ItemsSource set to a collection of 2,000 data items. Each data itemis a CLR object that has six string properties. To display each item, we used aDataTemplate that binds six text boxesto each of the six properties.

Organization

The point of the sample application is toshow a reference implementation for each performance optimization that was describedearlier. It has one class for each section of the document.

Section

Class

Non-Virtualized UI

none

UI Virtualization

ListBox

Recycling Containers

ContainerList

Faster Bindings

ContainerListFastBinding

Table 4. Sample application overview

ContainerList is a FrameworkElementthat looks somewhat likeListBox andimplements a simple container-recycling scheme.

ContainerListFastBinding is a subclass of ContainerListthat implements the data-binding optimizations that are described in the "FasterBindings" section.

"Real"Performance Numbers

Disclaimer: The numbers here are illustrative only. The improvements of eachmethod are largely dependent on your data set, the number of items that are init, and so on.

Control

Time to scroll 150 pages (sec)

ListBox

2.1

ContainerList

1.5

ContainerListFastBinding

1.1

Table 5. Sample application performance

Modifying the sample application to displayimages instead of text boxes shows a smaller improvement. This is so, becauseeach image is decoded as it is brought into view (a comparatively expensiveoperation, compared to container instantiation). The sample application hasonly one image with one binding, so that the data-binding optimization hadminimal impact.

Control

Time to scroll 150 pages (sec)

ListBox

5.2

ContainerList

4.6

Table 6. Sample application performance with images

This article focused mostly on describingthe bottlenecks in WPF. As Table 6 shows, if your application also doessignificant work while scrolling, you'll see less of a benefit. Thus, it isimportant to profile your application, to understand where the most importantbottlenecks are.

Resources

Good background information on this subjectcan be found on the Web:

·        Beatriz Costa's Blog (part threeof a three-part blog post, and thorough discussion ofTreeView performance for large data sets):

o  http://www.beacosta.com/blog/?p=45

·        "VirtualizingStackPanel Class" (overview):

o  http://msdn2.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.aspx

·        Dan Crevier's Blog (blog postson implementing a virtualizing tile panel, which could be helpful for readers whohope to apply the concepts in this article to 2-D layouts):

o  http://blogs.msdn.com/dancre/archive/tags/VirtualizingTilePanel/default.aspx

·        "Analyzing ApplicationPerformance" (using the profiling tools that are included in Microsoft VisualStudio 2005 Team System):

o  http://msdn2.microsoft.com/en-us/library/z9z62c29(VS.80).aspx

·        "Optimizing WPFApplication Performance" (general performance tips):

o  http://msdn2.microsoft.com/en-us/library/aa970683.aspx

Acknowledgements

The performance suggestions and sample application in this articleare based largely on code by Ben Carter, Atanas Koralski, and Kevin Moore ofthe WPF team. Jossef Goldberg modified the sample application to display imagesinstead of text.

About the Author

Cedric Dussud is a Software Development Engineer at Microsoft Corporation.

 

原创粉丝点击