How to Load a Billion Rows in a WPF Datagrid

Chelsea Devereaux - Aug 19 '22 - - Dev Community

Support for large data sets is one of the most requested features for UI controls and one of our key investment areas. When dealing with large datasets, there are two critical things to consider: the memory footprint and the performance. The best way to solve this is with data virtualization - which allows you to have an overall view of the entire data set without needing it all physically in one location.

Common data virtualization solutions often include client-side pagination or cursor lists. These techniques, however, bring usability implications to users, and, in most cases, we wish to have the ease of scrolling the whole dataset like normal.

With our .NET 6 FlexGrid for WPF, we had effectively implemented solutions to quickly work with multiple millions of rows. Still, we discovered significant issues with performance and memory when it reached one billion rows.

Check out in action here!

So, we decided to push the performance limitations further by implementing new techniques and overcoming every tiny bottleneck to tackle this and allow any number of rows to be displayed in FlexGrid.

The first thing to consider when wanting to display that number of items is that the dataset cannot be all in memory. Creating a List of 1 billion nulls generally takes around 15 seconds and consumes 8GB of memory. Therefore, it requires a data virtualization solution.

For this purpose, we provide the abstract C1VirtualDataCollection class, which is part of the ComponentOne DataCollection library. You can inherit this collection to provide your virtualized content. An example implementation looks like this:

        public class VirtualModeDataCollection : C1VirtualDataCollection<Customer>
        {
            public int TotalCount { get; set; } = 1_000_000_000;

            protected override async Task<Tuple<int, IReadOnlyList<Customer>>> GetPageAsync(int pageIndex, int startingIndex, int count, IReadOnlyList<SortDescription> sortDescriptions = null, FilterExpression filterExpression = null, CancellationToken cancellationToken = default(CancellationToken))
            {
                return new Tuple<int, IReadOnlyList<Customer>>(TotalCount, Enumerable.Range(startingIndex, count).Select(i => new Customer(i)).ToList());
            }
        }
Enter fullscreen mode Exit fullscreen mode

To test the performance and memory footprint, we will create the collection and load the first page, which is similar to what happens when visualizing the collection in FlexGrid. And we will repeat the same for the previous collection and the one containing the improvements.

Performance (ms) C1DataCollection 1.0.20221.66 C1DataCollection 1.0.20222.87
1 hundred 309 306
10 hundred 302 304
100 hundred 310 313
1 million 320 312
10 million 481 301
100 million 1,910 306
1 billion 15,777 313

Performance of C1VirtualDataCollection

Memory (bytes) C1DataCollection 1.0.20221.66 C1DataCollection 1.0.20222.87
1 hundred 19,776 19,608
10 hundred 143,240 29,920
100 hundred 1,061,096 29,936
1 million 8,401,408 27,672
10 million 134,230,344 36,264
100 million 1,073,754,680 35,960
1 billion 8,589,947,776 36,400

Memory Footprint of C1VirtualDataCollection

As you can see, the performance for loading the first page in the new version is unaltered, and the memory footprint is insignificant, even for the billion-items case. The latest version of C1VirtualDataCollection now uses a dynamic list internally that takes almost nothing to be created and uses minimal memory.

Skeleton Loading in FlexGrid

While most of the performance enhancements are within the DataCollection, there are some improvements in FlexGrid.

We took this opportunity to introduce another new feature in FlexGrid that helps with a perceived performance called Skeleton Loading. This feature shows an animated “skeleton” placeholder inside every cell, indicating the data is being loaded underneath. You can enable this feature in FlexGrid through behavior as seen below:

    <c1:FlexGrid>
        <i:Interaction.Behaviors>
            <c1:SkeletonLoadingBehavior />
        </i:Interaction.Behaviors>
    </c1:FlexGrid>
Enter fullscreen mode Exit fullscreen mode

Billion Rows

FlexGrid Overcomes WPF Layout Limits

Another improvement to FlexGrid was concerning rendering cells correctly for very large offsets.

As we worked out the scrolling performance in FlexGrid, we found some limits in the platform in terms of positioning the elements in the layout with huge numbers. We discovered that the content began to look displaced when rendering more than 2 million rows (30 pixels per row).

We reproduced this limitation with a ScrollViewer and simple TextBlocks. When the positioning goes past 60 million pixels or 2 million rows, the TextBlocks are positioned incorrectly.

ScrollViewer

So, we discovered that ScrollViewer is only good at positioning items up to a certain length, up to about 60 million pixels. After about 9 million rows (270 million pixels), the text stops rendering completely. If you are curious to try this yourself, you can download the sample here.

This limitation was overcome in FlexGrid 2022 v2 as we can go well beyond even 9 million rows to display up to one billion rows successfully. In theory, our WPF Datagrid can display more rows, but we stopped at one billion for this demonstration.

You can download the ComponentOne 2022 v2 update of WPF Edition to see the new FlexGrid Virtual Mode sample and see for yourself how it performs with one billion rows.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .