WPF Performance and .NET Framework Client Profile

WPF performance and .NET Framework Client Profile related blogs provided by Jossef Goldberg.

August, 2008

  • WPF Performance and .NET Framework Client Profile

    Improving Microsoft DataGrid CTP sorting performance - Part 2

    • 5 Comments

    In this blog I wrote how you can improve Microsoft DataGrid CTP by providing your own custom sort.  
    As one of the comments suggested you can get even better performance by using delegates.
    I have changed MySort code below and I now use delegates to avoid the switch comparison that was called on every compare during the QuickSort . I can now see additional ~50% speed gain during sort.

    In my 300,000 rows DataGrid, before I saw sort speed decreased from almost 5 minutes to 2.4 seconds and now the String type columns (e.g. Name, Position) are sorted in 1.4 sec while the Integer type columns (e.g. Id) are now sorted in 400 ms!

       1:  public class MySort : IComparer
       2:  {
       3:        public delegate int TwoArgDelegate(Employee arg1, Employee arg2);
       4:        TwoArgDelegate myCompare;
       5:        public MySort(ListSortDirection direction, DataGridColumn column)
       6:        {
       7:                  int dir = (direction == ListSortDirection.Ascending) ? 1 : -1;
       8:                  //set a delegate to be called by IComparer.Compare
       9:                  switch ((string)column.Header)
      10:                  {
      11:                      case "Id":
      12:                          myCompare = (a, b) => { return a.Id.CompareTo(b.Id) * dir; };
      13:                          break;
      14:                      case "Name":
      15:                          myCompare = (a, b) => { return a.Name.CompareTo(b.Name) * dir; };
      16:                          break;
      17:                      case "Position":
      18:                          myCompare = (a, b) => { return a.Position.CompareTo(b.Position) * dir; };
      19:                          break;
      20:                      case "Tel":
      21:                          myCompare = (a, b) => { return a.Telephone.CompareTo(b.Telephone) * dir; };
      22:                          break;
      23:                      case "Email":
      24:                          myCompare = (a, b) => { return a.Email.CompareTo(b.Email) * dir; };
      25:                          break;
      26:                      case "Enabled":
      27:                          myCompare = (a, b) => { return a.Enabled.CompareTo(b.Enabled) * dir; };
      28:                          break;
      29:                      case "City":
      30:                          myCompare = (a, b) => { return a.City.CompareTo(b.City) * dir; };
      31:                          break;
      32:                      case "Country":
      33:                          myCompare = (a, b) => { return a.Country.CompareTo(b.Country) * dir; };
      34:                          break;
      35:                      default:
      36:                          myCompare = (a, b) => { return 0; };
      37:                          break;
      38:                  }
      39:              }
      40:              int IComparer.Compare(object X, object Y)
      41:              {
      42:                  return myCompare((Employee)X, (Employee)Y);
      43:              }
      44:      }
      45:  }
  • WPF Performance and .NET Framework Client Profile

    Improving Microsoft DataGrid CTP sorting performance

    • 9 Comments
    Summary:

    As you may know Microsoft released a Community Tech Preview (CTP) of the DataGrid control. See the posting here.
    Once you start using the DataGrid CTP, load many elements to it and you use the default, built-in sort (by simply clicking on the DataGrid column headers) you may notice that sorting can be very slow.
    The reason is that the comparer that is built-in to the MS DataGrid knows nothing about your data type and currently use reflection which is very costly.

    In this blog I wanted to point you to a simple approach that could significantly improve the sorting performance of your DataGrid.

    Approach:

    If you load many elements to the MS DataGrid and use the built-in sort which currently use reflection, you may find sorting to be very slow.

    Fortunately, for many scenarios you can implement your IComparer and provide it in the CustomSort property. You could achieve significant performance gains by doing so, in my example I was able to achieve 100x speed improvement.

    In my 300,000 rows DataGrid, sort speed decreased from almost 5 minutes to 2.4 seconds !
    (The provided download only loads 6,000 rows, but you are welcome to change...)

    image

    Code:
    public partial class Window1 : System.Windows.Window
    {
        private bool UseCustomSort
        {
            get { return CustomSortCB.IsChecked == true; }
        }
     
        private void WPF_DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
        {
            if (_stopwatch == null)
            {
                _stopwatch = new Stopwatch();
            }
            else
            {
                _stopwatch.Reset();
            }
     
            _stopwatch.Start();
            if (UseCustomSort)
            {
                e.Handled = true;   // prevent the built-in sort from sorting
                PerformCustomSort(e.Column);
                CustomSortingDone();
            }
            else
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Normal, new NoArgDelegate(StandardSortingDone));
            }
        }
     
        private void StandardSortingDone()
        {
            _stopwatch.Stop();
            statusTextBlock1.Text = "Buit-in Sort: " + _stopwatch.ElapsedMilliseconds.ToString() + " msec";
        }
        private void CustomSortingDone()
        {
            _stopwatch.Stop();
            statusTextBlock2.Text = "Custom Sort: " + _stopwatch.ElapsedMilliseconds.ToString() + " msec";
        }
        private void PerformCustomSort(DataGridColumn column)
        {
     
            ListSortDirection direction = (column.SortDirection != ListSortDirection.Ascending) ? 
                                     ListSortDirection.Ascending : ListSortDirection.Descending;
            column.SortDirection = direction;
            ListCollectionView lcv = (ListCollectionView)CollectionViewSource.GetDefaultView(WPF_DataGrid.ItemsSource);
            MySort mySort = new MySort(direction, column);
            lcv.CustomSort = mySort;  // provide our own sort
        }
     
        public class MySort : IComparer
        {
            public MySort(ListSortDirection direction, DataGridColumn column)
            {
                Direction = direction;
                Column = column;
            }
     
            public ListSortDirection Direction
            {
                get;
                private set;
            }
     
            public DataGridColumn Column
            {
                get;
                private set;
            }
     
            int StringCompare(string s1, string s2)
            {
                if (Direction == ListSortDirection.Ascending)
                    return s1.CompareTo(s2);
                return s2.CompareTo(s1);
            }
     
            int IComparer.Compare(object X, object Y)
            {
                int int1, int2;
                string str1, str2;
                switch ((string)Column.Header)
                {
                    case "Id":
                        int1 = ((Employee)(X)).Id;
                        int2 = ((Employee)(Y)).Id;
                        if (Direction == ListSortDirection.Ascending)
                            return int1.CompareTo(int2);
                        return int2.CompareTo(int1);
                    case "Name":
                        str1 = ((Employee)X).Name;
                        str2 = ((Employee)Y).Name;
                        return StringCompare(str1, str2);
                    // ... do same for other columns
         
                  }
                return 0;
            }
        }
       private Stopwatch _stopwatch;
    }

    XAML Code:
    <Window x:Class="DataGridSortDemo.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:DataGridSortDemo"
        xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
        xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
        Title="Microsoft DataGrid Sort Example" Height="760" Width="850"
        >
        <Window.Resources>
            <local:Employees x:Key="employees" />
        </Window.Resources>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition MinHeight="25" Height="Auto"/>
          <RowDefinition MinHeight="3in" Height="*" />
          <RowDefinition Height="Auto" MinHeight="10" />
          <RowDefinition Height="Auto" MinHeight="10" />
          <RowDefinition Height="Auto" MinHeight="10" />
          <RowDefinition Height="Auto" MinHeight="10" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
     
     
        <dg:DataGrid x:Name="WPF_DataGrid" FontSize="12pt"
                     Grid.Row="1" Grid.Column="0"  Margin="10,2,10,2"
                     AutoGenerateColumns="False"
                     Sorting="WPF_DataGrid_Sorting"
                     ItemsSource="{StaticResource employees}"
                  >
                
          <dg:DataGrid.Columns>
            <dg:DataGridTextColumn Width="Auto" Header="Id" DataFieldBinding="{Binding Path=Id}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Name" DataFieldBinding="{Binding Path=Name}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Position" DataFieldBinding="{Binding Path=Position}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Tel" DataFieldBinding="{Binding Path=Telephone}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Email" DataFieldBinding="{Binding Path=Email}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Enabled" DataFieldBinding="{Binding Path=Enabled}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="City" DataFieldBinding="{Binding Path=City}"></dg:DataGridTextColumn>
            <dg:DataGridTextColumn Width="Auto" Header="Country" DataFieldBinding="{Binding Path=Country}"></dg:DataGridTextColumn>
         </dg:DataGrid.Columns>
        </dg:DataGrid>
     
        <TextBlock Grid.Row="2" Name="statusTextBlock1" Foreground="Red"
                   FontSize="12pt" Text="" />
        <TextBlock Grid.Row="3" Grid.Column="0" Name="statusTextBlock2" Foreground="Green"
                   FontSize="12pt"  Text=""  />
        <CheckBox  Name="CustomSortCB" FontWeight="Bold"  FontSize="12pt" Grid.Row="0" Grid.Column="0" Margin="10,6,6,6"
                   Foreground="Blue">
          Use Custom Sort  [Check / Uncheck box, then Click on the DataGrid header to compare Sort performance]
        </CheckBox>
     
     
      </Grid>
    </Window>
    Caveats:

    You can only use the CustomSort if your DataGrid is bound to an IList. You cannot use the CustomSort if you bound to an instance of IBindingList such as DataView. In this case the class that derives from ICollectionView is a BindingListCollectionView which does not have a custom sort property. The view in this case delegates the Sort logic to the underlying DataView which does not allow sort customization.

  • WPF Performance and .NET Framework Client Profile

    What’s new in WPF 3.5 SP1: Splash Screen to improve perceived startup perf

    • 13 Comments

    Summary:

    To improve the perception of a more responsive startup experience many WPF applications added a Splash Screen to their startup.  The Splash Screen, which does not load WPF code shows as soon as possible and is displayed until the application main window is rendered.
    Up until 3.5 Sp1 we pointed developers to sample code available on this blog.
    In  the just released .Net 3.5 SP1 we added basic Splash Screen support and new APIs and we recommend that you take advantage of it. (See download locations below)

    A Visual Studio 2008 Sp1 Item Template that makes adding a Splash Screen much easier is available on WPF Futures site on www.codeplex.com/wpf.

    How to Use:

         Approach A:

    The easiest way to add is by using VS 2008  SP1.
    ·  Create a new Standalone VB or C# WPF project in VS 2008 Sp1.  Verify “Target Framework” project property is “.Net Framework 3.5”

    ·   Add an image (your splash screen image) to the project (e.g. by drag and drop) 

    ·   Set the image BuildAction to SplashScreen

    ·   Do F5 ( Build+Run), splash screen should be shown.

    ·   To disable the Splash Screen image functionality you can either:

       o    Remove the “SplashScreen.png” image from the project, or

       o Set the splash image BuildAction to ‘None’.

    image

    Upon compile, the build (PresentationBuildTasks.dll) will generates below code in App.g.cs:

       1: SplashScreen splashScreen = new SplashScreen("Splashscreen1.png");
       2: splashScreen.Show(true);
       3: MyApp.App app = new MyApp.App();
       4: app.InitializeComponent();
       5: app.Run();

    Note:  Above code will be generated in VS 2008 Sp1 only if the “Target Framework” project property is “.Net Framework 3.5”. Otherwise no code will be generated.

    Approach B:

    Install the Splash Screen Visual Studio 2008 Sp1 Item Template from the WPF Futures site.

    ·   Create a new Standalone VB or C# WPF project in VS 2008 Sp1.  Verify “Target Framework” project property is “.Net Framework 3.5”

    ·   In VS,  Right-Click on your Project and then select “Add / New Item… / Splash Screen (WPF)”.

    ·   Notice the new image BuildAction  is set to SplashScreen for you.

    ·   Do F5 (Build+Run), the default splash screen image that was installed with the template will be shown (you likely want to replace with your own Splash Screen image)

    image

    Approach C:

    Another approach is to directly use the new public Splash Screen APIs. With this approach you can set the ‘BuildAction=Resource’.
    For example you can call the following code:

       1: SplashScreen appSplash = new SplashScreen("Splashscreen1.png");
       2: appSplash.Show(false);
       3: //….do some work…
       4: appSplash.Close( TimeSpan.FromSeconds(0.3)); // Splash to fadeout in 300ms

    The WPF Splash Screen APIs are defined as below:

       1: namespace System.Windows
       2: {
       3:     public class SplashScreen
       4:     {
       5:         public SplashScreen(string resourceName);
       6:         public SplashScreen(Assembly resourceAssembly, string resourceName);
       7:         public void Show(bool autoClose);
       8:         public void Close(TimeSpan fadeOutDuration);    
       9:     }
      10: }

    public ApplicationSplashScreen(string resourceName)

    Behavior

    Constructor.  resourceName points to the embedded splash image

    Exceptions

     none

    public ApplicationSplashScreen(Assembly resourceAssembly, string resourceName)

    Behavior

    Constructor. resourceAssembly specify the assembly in which resources lives resourceName points to the embedded splash image

    Exceptions

     none

    public void Show(bool autoClose);

    Behavior

    Constructor. Shows the splash image.

    If autoClose is true, the code uses the dispatcher to queue an item at Idle priority, enabling the Splash screen to start fading out using the default fadeOutDuration (300 msec) after the application first renders. 

    If autoClose is false, the app is responsible to call Close(fadeOutDuration), in this case the splash screen fades out using the provided fadeOutDuration.

    Exceptions

     IOException if resource provided in constructor not found in app assembly

    public void Close(TimeSpan fadeOutDuration)

    Behavior

    Closes the splash window. 

    fadeOutDuration is the time for splash screen to fade out. If autoClose is true, WPF will close the window and use a 300 msec as the default fadeOutDuration.

    Exceptions

    none

    Caveats:

    It is important to understand that the Splash Screen has the following limitations:
    ·         It does not actually improve application cold startup time.
    ·         Cannot display sequence of splash images or animation.
    ·         Does not support for animated GIFs
    ·         Does not support earlier versions of VS (earlier than VS2008 Sp1). You should still be able to use the APIs (e.g. Approach C)

    ·         Does not support XBAPs (XBAPs already have their own improved coldstart progress page in  .Net 3.5 Sp1)
    ·         Does not have  XAML support for setting splash screen image
    ·         Expression Blend does not provide UI to  set the SplashScreen build action

    .Net 3.5 SP1 Download locations:

    Content

    Links

    Visual Studio 2008 Express Editions with Service Pack 1 (Bootstrappers)

    http://go.microsoft.com/fwlink/?LinkId=123679

    Visual Studio 2008 Express Editions with Service Pack 1 (iso)

    http://go.microsoft.com/fwlink/?LinkId=123680

    Visual Studio 2008 Service Pack 1 (Bootstrapper)

    http://go.microsoft.com/fwlink/?LinkId=122094

    Visual Studio 2008 Service Pack 1 (iso)

    http://go.microsoft.com/fwlink/?LinkId=122095

    Visual Studio  Team System 2008 Team Foundation Server Service Pack 1

    http://go.microsoft.com/fwlink/?LinkId=124829

    .NET Framework 3.5 Service Pack 1

    http://go.microsoft.com/fwlink/?LinkId=124150

     

Page 1 of 1 (3 items)