I’m writing a program that uses a bunch of two-way bindings and the amount of memory used has become a huge problem. In my full application, I start at 50Mb, and then, just by using the bindings (i.e. changing the value on one side and letting the binding update the other side), I usually break 100Mb, even though my code has not allocated anything new. My question is what this extra memory is and what I can do to control it. I have created a simple, reproducible example below:
Say I have a main window with the following contents:
<StackPanel Height="25" Orientation="Horizontal">
<TextBox UndoLimit="1" Name="TestWidth" />
<Label>,</Label>
<TextBox UndoLimit="1" Name="TestHeight" />
</StackPanel>
And then in this window’s constructor, I generate a new window, show it, and then bind it’s WidthProperty and HeightProperty dependency properties to variables that utilize INotifyPropertyChanged:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private int _WidthInt;
public int WidthInt
{
get { return _WidthInt; }
set { _WidthInt = value; NotifyPropertyChanged("WidthInt"); }
}
private int _HeightInt;
public int HeightInt
{
get { return _HeightInt; }
set { _HeightInt = value; NotifyPropertyChanged("HeightInt"); }
}
public MainWindow()
{
InitializeComponent();
Window testWindow = new Window();
testWindow.Show();
Binding bind = new Binding("HeightInt");
bind.Source = this;
bind.Mode = BindingMode.TwoWay;
testWindow.SetBinding(Window.HeightProperty, bind);
//bind.Converter = new convert();
//this.TestHeight.SetBinding(TextBox.TextProperty, bind);
bind = new Binding("WidthInt");
bind.Source = this;
bind.Mode = BindingMode.TwoWay;
testWindow.SetBinding(Window.WidthProperty, bind);
//bind.Converter = new convert();
//this.TestWidth.SetBinding(TextBox.TextProperty, bind);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
GC.Collect();
}
}
Then if I constantly resize the window, my memory usage in the task manager linearly increases with no apparent upper limit. The program starts at 17Mb, and within 30 seconds of resizing it increases to 20Mb and hovers after a certain point in the low 20’s (thanks Ian). This actually happens even if there are no bindings, and the memory does not go back down. While annoying, this isn’t the “memory leap” I’m talking about.
If I uncomment the lines that also bind the textboxes to the variables, I get the following result: in just a few seconds it jumps from 18Mb to 38Mb and then hovers there (Please note setting the binding for the textbox in the XAML does not affect the memory spike). I tried implementing my own converter for the text box binding, but this does not affect the memory usage.
The jump still exists if I change the variables to new dependency properties and bind to them, e.g.
public static readonly DependencyProperty WidthIntProperty = DependencyProperty.Register("WidthIntProperty", typeof(int), typeof(MainWindow), new UIPropertyMetadata(0, null));
int WidthInt
{
get { return (int)this.GetValue(WidthIntProperty); }
set { this.SetValue(WidthIntProperty, value); }
}
...
Binding bind = new Binding("Text");
bind.Source = TestHeight;
bind.Mode = BindingMode.TwoWay;
this.SetBinding(MainWindow.HeightIntProperty, bind);
testWindow.SetBinding(Window.HeightProperty, bind);
or if I bind directly between the text property and the width dependency property and use BindingMode.OneWay or vise versa.
Using a CLR profiler does not seem to show me what’s being allocated and I don’t have access to a commercial memory profiler. Can someone explain to me what is being kept in memory and how I can get rid of it while still having the functionality of a continuous BindingMode? Do I have to implement my own binding method and do the event handling myself? Or is there something I can regularly flush outside of the GC?
Thank you for your time.
One thing to remember with simple programs like this is that a small amount of code can end up hitting a fairly large amount of WPF’s infrastructure. E.g., with a single
TextBoxyou’re using the layout engine, the property engine, the templating system, the styling system, and accessibility features. The moment you start to type, you also bring in the input system, the typography support, and some non-trivial internationalization infrastructure.Those last two may well be a large part of what you’re seeing in this particular example. WPF automatically exploits a lot of OpenType font features, which requires it to do a lot of work under the covers. (As it happens the default UI font doesn’t actually do a lot with it, but you still end up paying the price of entry for the code that discovers that Segoe UI is not a very interesting typeface.) It’s a comparatively expensive feature for saying how subtle a difference it makes. Likewise, it’s amazing how much goes into locale-aware input handling – getting that comprehensively right with full i8n support is more work than most people imagine.
You’ll probably pay the price for each of these subsystems eventually, because the
TextBoxis not the only piece of WPF to use them. So the effort required for hand-built solutions that attempt to avoid them may ultimately be for nothing.Tiny test applications paint a misleading picture – in a real application the price payed is shared out a bit better. Your first
TextBoxmight have cost your 30MB, but you’ve now paged in a load of stuff that the rest of your application was going to use anyway. Had you started with an application that uses nothing but aListBox, you could then add aTextBoxand compare the difference in memory consumption to theListBox-only baseline. This would probably give you a rather different picture of the marginal cost of adding aTextBoxto your application.So outside of the context of a trivial test application, the effort required to write your own text box is likely to produce very little difference in private working set in practice. You’ll almost certainly end up paging in all of the features and systems I mentioned in the first paragraph eventually, because
TextBoxis not the only thing in WPF to use them.Could each of these systems be more frugal? No doubt they could, but sadly, WPF hasn’t had as much engineering input as I’d like, what with the distraction that is Silverlight, not to mention the rumours that yet another attempt at a UI framework is coming in Win8… High memory usage is, unfortunately, a feature of WPF. (Although bear in mind that a WPF app will also tend to use more memory on a machine with more memory. It takes some memory pressure before its working set gets driven to its most efficient level.)