J’ajoute quelques milliers (par exemple 53 709) éléments à un WinForms ListView.
Tentative 1 : 13,870 ms
foreach (Object o in list) { ListViewItem item = new ListViewItem(); RefreshListViewItem(item, o); listView.Items.Add(item); }
Cela fonctionne très mal. La première solution évidente consiste à appeler BeginUpdate/EndUpdate
.
Tentative 2 : 3,106 ms
listView.BeginUpdate(); foreach (Object o in list) { ListViewItem item = new ListViewItem(); RefreshListViewItem(item, o); listView.Items.Add(item); } listView.EndUpdate();
C’est mieux, mais toujours un ordre de grandeur trop lent. Séparons la création de ListViewItems de l’ajout de ListViewItems, nous trouvons donc le véritable coupable:
Tentative 3 : 2,631 ms
var items = new List(); 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()
Le véritable goulot d’étranglement consiste à append les éléments. Essayons de le convertir en AddRange
plutôt qu’en foreach
Tentative 4: 2,182 ms
listView.BeginUpdate(); listView.Items.AddRange(items.ToArray()); listView.EndUpdate();
Un peu mieux. Soyons sûr que le goulot d’étranglement n’est pas dans le ToArray()
Tentative 5: 2,132 ms
ListViewItem[] arr = items.ToArray(); stopwatch.Start(); listView.BeginUpdate(); listView.Items.AddRange(arr); listView.EndUpdate(); stopwatch.Stop();
La limitation semble append des éléments à la vue de liste. Peut-être l’autre surcharge de AddRange
, où nous ajoutons un ListView.ListViewItemCollection
plutôt qu’un tableau
Tentative 6: 2,141 ms
listView.BeginUpdate(); ListView.ListViewItemCollection lvic = new ListView.ListViewItemCollection(listView); lvic.AddRange(arr); listView.EndUpdate();
Eh bien, ce n’est pas mieux.
Maintenant il est temps de s’étirer:
Étape 1 – assurez-vous qu’aucune colonne n’est définie sur “auto-width” :
Vérifier
Étape 2 – assurez-vous que ListView n’essaie pas de sortinger les éléments chaque fois que j’en ajoute un:
Vérifier
Étape 3 – Demander un stackoverflow:
Vérifier
Note: Évidemment, cette ListView n’est pas en mode virtuel; puisque vous ne pouvez / ne pouvez pas “append” d’éléments à une vue de liste virtuelle (vous définissez la VirtualListSize
). Heureusement, ma question ne concerne pas une vue de liste en mode virtuel.
Y a-t-il quelque chose qui me manque qui pourrait expliquer l’ajout d’éléments à la listview étant si lente?
Chatter Bonus
Je sais que la classe Windows ListView peut faire mieux, car je peux écrire du code qui le fait en 394 ms
:
ListView1.Items.BeginUpdate; for i := 1 to 53709 do ListView1.Items.Add(); ListView1.Items.EndUpdate;
qui, comparé au code C # équivalent 1,349 ms
:
listView.BeginUpdate(); for (int i = 1; i <= 53709; i++) listView.Items.Add(new ListViewItem()); listView.EndUpdate();
est un ordre de grandeur plus rapide.
Quelle propriété du wrapper WinForms ListView suis-je manquant?
J’ai jeté un coup d’oeil au code source de la vue liste et j’ai remarqué quelques petites choses qui peuvent ralentir la performance d’un facteur 4 ou plus:
dans ListView.cs, ListViewItemsCollection.AddRange
appelle ListViewNativeItemCollection.AddRange
, qui est l’endroit où j’ai commencé mon audit
ListViewNativeItemCollection.AddRange
(de la ligne: 18120) a deux passages dans la collection entière de valeurs, un pour collecter tous les éléments vérifiés et les “restaurer” après l’appel de InsertItems
(ils sont tous deux protégés par une vérification du owner.IsHandleCreated
, propriétaire étant le ListView
) appelle alors BeginUpdate
.
ListView.InsertItems
(de la ligne: 12952), premier appel, a un autre cheminement de la liste entière, puis ArrayList.AddRange est appelé (probablement un autre passage), puis un autre passage après. Menant à
ListView.InsertItems
(à partir de la ligne: 12952), deuxième appel (via EndUpdate
), un autre passage à travers lequel ils sont ajoutés à un HashTable
, et un Debug.Assert(!listItemsTable.ContainsKey(ItemId))
le ralentira davantage en mode débogage. Si le handle n’est pas créé, il ajoute les éléments à une ArrayList
, listItemsArray
mais if (IsHandleCreated)
, il appelle
ListView.InsertItemsNative
(de la ligne: 3848) passe à travers la liste où il est réellement ajouté à la vue de liste native. un Debug.Assert(this.Items.Contains(li)
ralentira également les performances en mode débogage).
Il y a BEAUCOUP de passages supplémentaires à travers toute la liste des éléments du contrôle .net avant même qu’il n’arrive réellement à insérer les éléments dans la vue de liste native. Certains passages sont protégés par des vérifications sur le Handle en cours de création. Si vous pouvez append des éléments avant la création du handle, cela peut vous faire gagner du temps. La méthode OnHandleCreated
prend le listItemsArray
et appelle directement InsertItemsNative
sans le listItemsArray
supplémentaire.
Vous pouvez lire le code ListView
dans la source de référence vous-même et jeter un coup d’oeil, peut-être que j’ai raté quelque chose.
Dans le numéro de mars 2006 de MSDN Magazine, il y avait un article intitulé Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps
.
Cet article contient notamment des conseils pour améliorer les performances de ListViews. Il semble indiquer que c’est plus rapide d’append des éléments avant que le descripteur ne soit créé, mais que vous payerez un prix lorsque le contrôle est rendu. Peut-être que l’application des optimisations de rendu mentionnées dans les commentaires et l’ajout des éléments avant la création du descripteur donneront le meilleur des deux mondes.
Edit: Testé cette hypothèse de différentes manières, et tout en ajoutant les éléments avant de créer le handle est plus rapide, il est exponentiellement plus lent quand il va créer le handle. J’ai joué en essayant de le piéger pour créer le handle, puis en quelque sorte le faire appeler InsertItemsNative sans passer par toutes les passes supplémentaires, mais hélas j’ai été contrecarré. La seule chose que je pourrais penser serait de créer votre Win32 ListView dans un projet c ++, de le bourrer avec des éléments et d’utiliser le hooking pour capturer le message CreateWindow envoyé par ListView lors de la création de son handle et de renvoyer une référence à win32. ListView au lieu d’une nouvelle fenêtre .. mais qui sait ce que le côté affecte il y aurait … un gourou Win32 aurait besoin de parler de cette idée folle 🙂
J’ai utilisé ce 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();
J’ai également défini GenerateMember
sur false pour chaque colonne.
Lien vers le sortingeur de vue de liste personnalisée: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter
J’ai le même problème. Ensuite, j’ai trouvé que le sorter
rendait si lent. Rendez la sortingeuse nulle
this.listViewAbnormalList.ListViewItemSorter = null;
alors quand cliquez sur sortingeur, sur la méthode ListView_ColumnClick
, le rendre
lv.ListViewItemSorter = new ListViewColumnSorter()
Enfin, après avoir été sortingé, rendre le sorter
nouveau nul
((System.Windows.Forms.ListView)sender).Sort(); lv.ListViewItemSorter = null;
Créez tous vos ListViewItems FIRST , puis ajoutez-les tout à la fois à ListView .
Par exemple:
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 );