Introduction
In my workplace (a financial institution), responsiveness of Windows application is critical and there are many factors which will affect the performance. These factors include network, dependent external services and software design.
In this tip, I would like to investigate and compare the performance on different ways to refresh data in a typical WPF application.
Background
In this example, there is a window full of WPF controls (I have added multiple instances of the same user control for simplicity). The user control contains a typical set of WPF controls, including Label
, TextBox
, Slider
, ComboBox
, CheckBox
, DataGrid
. This is a typical MVVM design.
View
- DogDetails.xaml user controlViewModel
- DogVM.csModel
- Dog.cs
There are four buttons at the top of the application. The functionality of these four buttons are the same, it refreshes the whole window with another set of data (a list of Dog
). However, the implementation of the three buttons are different. The aim is to test and compare the performance of each of them.
1. Re-Render and Re-Bind
- Re-generate the data (list of
Dog
object) - Re-constructs all the
ViewModel
s, one per DogDetails
user control - Re-constructs all
UserControl
s and rebind to ViewModel
s
2. Re-Construct VM and Re-Bind
- Re-generate the data (list of
Dog
object) - Re-constructs all the
ViewModel
s, one per DogDetail
s user control - Re-use
UserControl
s and rebind to ViewModel
s
3. Re-use VM and Re-Bind
- Re-generate the data (list of
Dog
object) - Re-use
ViewModel
s and set the Model
referenced by each ViewModel
- Re-use
UserControl
s and rebind to ViewModel
s
4. Re-Notify Only
- Re-generate the data (list of
Dog
object) - Re-use all the
ViewModel
s and UserControl
s by just switching the Dog
object referenced by DogVM
and call NotifyPropertyChanged
for all fields
To measure the performance of rendering, I followed the advice from here.
Using the Code
Here are the various implementations of refreshing. They are all in MainWindow.cs:
1. Re-render control and Re-bind
public void RegenAndBind()
{
allVMs.Clear();
allDetailsControls.Clear();
start = DateTime.Now;
int i = 0;
for (int row = 1; row < _mainGrid.RowDefinitions.Count; row++)
{
for (int col = 0; col < _mainGrid.ColumnDefinitions.Count; col++)
{
DogVM thisvm = new DogVM();
thisvm.SetDog(allitems[i]);
var dogDetails = new DogDetails();
dogDetails.DataContext = thisvm;
_mainGrid.Children.Add(dogDetails);
Grid.SetColumn(dogDetails, col);
Grid.SetRow(dogDetails, row);
allVMs.Add(thisvm);
allDetailsControls.Add(dogDetails);
i++;
}
}
}
2. Re-construct VM and Re-bind
public void ReconstuctVMAndbindOnly()
{
allVMs.Clear();
start = DateTime.Now;
int i = 0;
for (int row = 1; row < _mainGrid.RowDefinitions.Count; row++)
{
for (int col = 0; col < _mainGrid.ColumnDefinitions.Count; col++)
{
DogVM thisvm = new DogVM();
thisvm.SetDog(allitems[i]);
allDetailsControls[i].DataContext = thisvm;
allVMs.Add(thisvm);
i++;
}
}
}
3. Re-use VM and Re-bind
public void RebindOnly()
{
start = DateTime.Now;
int i = 0;
for (int row = 1; row < _mainGrid.RowDefinitions.Count; row++)
{
for (int col = 0; col < _mainGrid.ColumnDefinitions.Count; col++)
{
allVMs[i].SetDog(allitems[i]);
allDetailsControls[i].DataContext = allVMs[i];
i++;
}
}
}
4. Re-notify only
public void NotifyOnly()
{
start = DateTime.Now;
int i = 0;
for (int row = 1; row < _mainGrid.RowDefinitions.Count; row++)
{
for (int col = 0; col < _mainGrid.ColumnDefinitions.Count; col++)
{
allVMs[i].SetDog(allitems[i]);
i++;
}
}
}
Points of Interest
Here's the performance figures I obtained from my moderate spec PC when the application is opened in full screen (Intel i5-2500K 3.3Ghz, 16GB ram, SSD on windows 7, VS2013)
I also note that having a DataGrid
in DogDetails.xaml has quite a big performance impact.
Here are the average of 10 refreshes for each implementation. (With DataGrid
in UserControl
)
- Re-render control and Re-bind: 1126.2ms
- Re-construct VM and Re-bind: 403.6ms
- Re-use VM and Re-bind: 373.8ms
- Re-notify only: 343.6ms
Here are the average of 10 refreshes for each implementation. (Without DataGrid
in UserControl
- comment the DataGrid
in DogDetails.xml):
- Re-render control and Re-bind: 680.1ms
- Re-construct VM and Re-bind: 144.4ms
- Re-use VM and Re-bind: 129.8ms
- Re-notify only: 96.6ms
Here's the summary of the findings:
DataGrid
rendering performance is quite poor, even when the dataset
is really small. I might investigate this further, maybe using a 3rd party grid. - Most of the performance gain is obtained by avoiding the reconstruction and building of the
UserControl
. - In this example, there is only a very small gain (around 10 to 30ms) after avoiding reconstruction of the
ViewModel
. This is because the ViewModel
is really simple in this example. The benefit is highly depending on the complexity and performance of the ViewModel
constructor. - Size of the window has a big effect on performance.