88

i'm adding a few thousand (e.g. 53,709) items to a WinForms ListView.

Attempt 1: 13,870 ms

foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}

This runs very badly. The obvious first fix is to call BeginUpdate/EndUpdate.

Attempt 2: 3,106 ms

listView.BeginUpdate();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   listView.Items.Add(item);
}
listView.EndUpdate();

This is better, but still an order of magnitude too slow. Let's separate creation of ListViewItems from adding ListViewItems, so we find the actual culprit:

Attempt 3: 2,631 ms

var items = new List<ListViewItem>();
foreach (Object o in list)
{
   ListViewItem item = new ListViewItem();
   RefreshListViewItem(item, o);
   items.Add(item);
}

stopwatch.Start();

listView.BeginUpdate();
    foreach (ListViewItem item in items)
        listView.Items.Add(item));
listView.EndUpdate();

stopwatch.Stop()

The real bottleneck is adding the items. Let's try converting it to AddRange rather than a foreach

Attempt 4: 2,182 ms

listView.BeginUpdate();
listView.Items.AddRange(items.ToArray());
listView.EndUpdate();

A bit better. Let's be sure that the bottleneck isn't in the ToArray()

Attempt 5: 2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

listView.BeginUpdate();
listView.Items.AddRange(arr);
listView.EndUpdate();

stopwatch.Stop();

The limitation seems to be adding items to the listview. Maybe the other overload of AddRange, where we add a ListView.ListViewItemCollection rather than an array

Attempt 6: 2,141 ms

listView.BeginUpdate();
ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView);
lvic.AddRange(arr);
listView.EndUpdate();

Well that's no better.

Now it's time to stretch:

  • Step 1 - make sure no column is set to "auto-width":

    enter image description here

    Check

  • Step 2 - make sure the ListView isn't trying to sort the items each time i add one:

    enter image description here

    Check

  • Step 3 - Ask stackoverflow:

    enter image description here

    Check

Note: Obviously this ListView is not in virtual mode; since you don't/cannot "add" items to a virtual list view (you set the VirtualListSize). Fortunately my question is not about a list view in virtual mode.

Is there anything i am missing that might account for adding items to the listview being so slow?


Bonus Chatter

i know the Windows ListView class can do better, because i can write code that does it in 394 ms:

ListView1.Items.BeginUpdate;
for i := 1 to 53709 do
   ListView1.Items.Add();
ListView1.Items.EndUpdate;

which when compared to the equivalent C# code 1,349 ms:

listView.BeginUpdate();
for (int i = 1; i <= 53709; i++)
   listView.Items.Add(new ListViewItem());
listView.EndUpdate();

is an order of magnitude faster.

What property of the WinForms ListView wrapper am i missing?

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 2
    Side note: If using Checkboxes you should set checked state before adding to the ListView. Initializing checked states http://blogs.msdn.com/b/hippietim/archive/2006/03/20/556007.aspx – Tim Schmelter Jan 25 '12 at 18:44
  • 3
    I have to ask: why are you adding THAT many items? – O.O Jan 25 '12 at 18:45
  • 4
    Great question Ian. Have you seen this blog on the subject? http://www.virtualdub.org/blog/pivot/entry.php?id=273 – Chris Shain Jan 25 '12 at 18:45
  • Have you tried a for instead of a foreach? – programad Jan 25 '12 at 18:47
  • @subt13 Because the promotional giveaway has, in this promotion, has 53,709 high-rollers invited. If there was only 10,000 people being invited, then the list would have only 10,000 items. – Ian Boyd Jan 25 '12 at 19:26
  • 2
    1,349 ms, impossible. I try with 53709 items it takes some minutes. Why use a list view with so many items? , is not really usable. You can use listBox or comboBox to increase speed but is an insane number – Xilmiki Jan 25 '12 at 19:27
  • 2
    @IanBoyd - For what reason do you need to display that many items, is my next question. Why not filter the list to a reasonable amount? This is why grids use pagers :). – O.O Jan 25 '12 at 19:29
  • @ChrisShain That's a very interesting blog post. i might should probably will try overriding the WinForms ListView to restore default Windows behavior. – Ian Boyd Jan 25 '12 at 19:30
  • 1
    @subt13 So the user can use click-sorting and instant search filtering to find the items they want. i don't want to limit the user-experience because of limitations in a WinForms control. Look at Picasa, hundreds of thousands of images all instantly available. – Ian Boyd Jan 25 '12 at 19:32
  • 1
    @IanBoyd - Not to be argumentative, but who knows how Picasa does it? You still do not have to populate the entire list in order to do click-sorting and instant search filtering. Not at all. I think your approach to this is waaay off. – O.O Jan 25 '12 at 19:35
  • Try to remove all columns and add items with no text at all. is it faster? – Davide Piras Jan 25 '12 at 19:37
  • @subt13 i was just saying that show all the items to the user is correct and good. i know the windows ListView control can handle such a thing, because it already can. What i need is how to do it in .NET. – Ian Boyd Jan 25 '12 at 20:35
  • @Xilmiki Are you saying that `1,349 ms` seems too high, and there might be a performance improvement that i'm missing? Or are you saying that you don't believe i was able to add 50 thousand items as fast as 1.3 seconds? – Ian Boyd Jan 25 '12 at 20:39
  • 4
    Why not use Virtual List View? Okay, you need to program how to retrieve item data and you might have to maintain other things like sorting, filtering etc. but you will fill the list view instantly no matter how many items. – Casperah Jan 25 '12 at 22:01
  • @Casperah i've started. But it will about 3 days of work to re-implement everything the ListView already does (only this time faster) – Ian Boyd Jan 26 '12 at 14:38
  • Have you tried to bind your data set to the control instead of iterating and adding each time? – user1231231412 Jan 26 '12 at 18:27
  • Hmm yeah, the WinForms implementation of the ListView control is known to be slow and in other ways problematic. The code for adding items is hardly optimized, and there's a lot of stuff going on behind the scenes. Generally, if you feel the need for speed, you won't write managed code. The alternatives are to either use virtual mode (probably a better choice anyway if you're displaying over 10,000 items...), or write your own wrapper around the Win32 ListView control without all the extra overhead. That's probably more work than it's worth, and it may still not fix the problem. – Cody Gray - on strike Jan 26 '12 at 18:59
  • 1
    Only other thing to check (although I hesitate, because I feel like I might be insulting you by even suggesting this, though I don't mean to) is that you *are* running these tests with optimizations enabled ("Release" mode). There's a lot of *extra* bounds checking that goes in in Debug mode that's going to slow things down even more. – Cody Gray - on strike Jan 26 '12 at 19:00
  • 1
    @CodyGray Too late for me to check it the old way now. i'm now hip-deep in re-creating a listview out of a virtual listview. It's hellva faster, with all the same functionality (sad really that .NET team got it so wrong). – Ian Boyd Jan 26 '12 at 21:52

5 Answers5

22

I took a look at the source code for the list view and I noticed a few things that may make the performance slow down by the factor of 4 or so that you're seeing:

in ListView.cs, ListViewItemsCollection.AddRange calls ListViewNativeItemCollection.AddRange, which is where I began my audit

ListViewNativeItemCollection.AddRange (from line: 18120) has two passes through the entire collection of values, one to collect all the checked items another to 'restore' them after InsertItems is called (they're both guarded by a check against owner.IsHandleCreated, owner being the ListView) then calls BeginUpdate.

ListView.InsertItems (from line: 12952), first call, has another traverse of the entire list then ArrayList.AddRange is called (probably another pass there) then another pass after that. Leading to

ListView.InsertItems (from line: 12952), second call (via EndUpdate) another pass through where they are added to a HashTable, and a Debug.Assert(!listItemsTable.ContainsKey(ItemId)) will slow it further in debug mode. If the handle isn't created, it adds the items to an ArrayList, listItemsArray but if (IsHandleCreated), then it calls

ListView.InsertItemsNative (from line: 3848) final pass through the list where it is actually added to the native listview. a Debug.Assert(this.Items.Contains(li) will additionally slow down performance in debug mode.

So there are A LOT of extra passes through the entire list of items in the .net control before it ever gets to actually inserting the items into the native listview. Some of the passes are guarded by checks against the Handle being created, so if you can add items before the handle is created, it may save you some time. The OnHandleCreated method takes the listItemsArray and calls InsertItemsNative directly without all the extra fuss.

You can read the ListView code in the reference source yourself and take a look, maybe I missed something.

In the March 2006 issue of MSDN Magazine there was an article called Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

This article contained tips for improving the performance of ListViews, among other things. It seems to indicate that its faster to add items before the handle is created, but that you will pay a price when the control is rendered. Perhaps applying the rendering optimizations mentioned in the comments and adding the items before the handle is created will get the best of both worlds.

Edit: Tested this hypothesis in a variety of ways, and while adding the items before creating the handle is suuuper fast, it is exponentially slower when it goes to create the handle. I played with trying to trick it to create the handle, then somehow get it to call InsertItemsNative without going through all the extra passes, but alas I've been thwarted. The only thing I could think might be possible, is to create your Win32 ListView in a c++ project, stuff it with items, and use hooking to capture the CreateWindow message sent by the ListView when creating its handle and pass back a reference to the win32 ListView instead of a new window.. but who knows what the side affects there would be... a Win32 guru would need to speak up about that crazy idea :)

jrh
  • 405
  • 2
  • 10
  • 29
Erikest
  • 4,997
  • 2
  • 25
  • 37
11

I used this code:

ResultsListView.BeginUpdate();
ResultsListView.ListViewItemSorter = null;
ResultsListView.Items.Clear();

//here we add items to listview

//adding item sorter back
ResultsListView.ListViewItemSorter = lvwColumnSorter;


ResultsListView.Sort();
ResultsListView.EndUpdate();

I have set also GenerateMember to false for each column.

Link to custom list view sorter: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter

bluish
  • 26,356
  • 27
  • 122
  • 180
Slav2
  • 111
  • 1
  • 3
  • 4
    Yes, having a *sorter* active while adding items is *** tremendously*** slow. But in this case i don't have a sorter. But this is a useful first step for people who might not realize that the .NET listview calls the sort *each time* an item is added - rather than at the end. – Ian Boyd Jul 09 '12 at 14:14
0

I have the same problem. Then I found it's sorter make it so slow. Make the sorter as null

this.listViewAbnormalList.ListViewItemSorter = null;

then when click sorter, on ListView_ColumnClick method , make it

 lv.ListViewItemSorter = new ListViewColumnSorter()

At last, after it's been sorted, make the sorter null again

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;
Batur
  • 529
  • 5
  • 9
-1

ListView Box Add

This is simple code I was able to construct to add Items to a listbox that consist of columns. The first column is item while the second column is price. The code below prints Item Cinnamon in first column and 0.50 in the second column.

// How to add ItemName and Item Price
listItems.Items.Add("Cinnamon").SubItems.Add("0.50");

No instantiation needed.

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Demetre Phipps
  • 107
  • 1
  • 3
-4

Create all your ListViewItems FIRST, then add them to the ListView all at once.

For example:

    var theListView = new ListView();
    var items = new ListViewItem[ 53709 ];

    for ( int i = 0 ; i < items.Length; ++i )
    {
        items[ i ] = new ListViewItem( i.ToString() );
    }

    theListView.Items.AddRange( items );
ahazzah
  • 756
  • 1
  • 7
  • 18