Pregunta ¿Cómo acelerar la adición de elementos a un ListView?


Estoy agregando algunos miles (por ejemplo, 53,709) elementos a un ListView de WinForms.

Intento 1: 13,870 ms

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

Esto funciona muy mal. La primera solución obvia es llamar BeginUpdate/EndUpdate.

Intento 2: 3,106 ms

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

Esto es mejor, pero sigue siendo un orden de magnitud demasiado lento. Vamos a separar la creación de ListViewItems de agregar ListViewItems, por lo que encontramos el verdadero culpable:

Intento 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()

El verdadero cuello de botella es agregar los artículos. Tratemos de convertirlo a AddRange preferible a foreach

Intento 4:  2,182 ms

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

Un poco mejor. Asegurémonos de que el cuello de botella no está en el ToArray()

Intento 5:  2,132 ms

ListViewItem[] arr = items.ToArray();

stopwatch.Start();

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

stopwatch.Stop();

La limitación parece ser agregar elementos a la vista de lista. Tal vez la otra sobrecarga de AddRange, donde agregamos un ListView.ListViewItemCollection en lugar de una matriz

Intento 6:  2,141 ms

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

Bueno, eso no es mejor.

Ahora es el momento de estirar:

  • Paso 1 - asegúrese de que ninguna columna esté configurada para "auto-ancho":

    enter image description here

    Comprobar

  • Paso 2 - Asegúrese de que ListView no intente ordenar los elementos cada vez que agregue uno:

    enter image description here

    Comprobar

  • Paso 3 - Pregunta a stackoverflow:

    enter image description here

    Comprobar

Nota: Obviamente, este ListView no está en modo virtual; ya que no puede / no puede "agregar" elementos a una vista de lista virtual (configura el VirtualListSize) Afortunadamente, mi pregunta no es acerca de una vista de lista en modo virtual.

¿Hay algo que me falta que podría explicar que la adición de elementos a la vista de lista sea tan lenta?


Bonus Chatter

Sé que la clase ListView de Windows puede hacerlo mejor, porque puedo escribir código que lo haga en 394 ms:

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

que cuando se compara con el código equivalente de C # 1,349 ms:

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

es un orden de magnitud más rápido.

¿Qué propiedad del contenedor WinForms ListView me falta?


75
2018-01-25 18:41


origen


Respuestas:


Eché un vistazo al código fuente de la vista de lista y noté algunas cosas que pueden ralentizar el rendimiento por el factor de 4 que estás viendo:

en ListView.cs, ListViewItemsCollection.AddRange llamadas ListViewNativeItemCollection.AddRange, que es donde comencé mi auditoría

ListViewNativeItemCollection.AddRange (desde la línea: 18120) tiene dos pasadas a través de la colección completa de valores, una para recoger todos los elementos marcados y otra para 'restaurarlos' después de InsertItems se llama (ambos están protegidos por un cheque en contra owner.IsHandleCreated, el dueño es el ListView) luego llama BeginUpdate.

ListView.InsertItems (desde la línea: 12952), primera llamada, tiene otra poligonal de la lista completa, luego se llama ArrayList.AddRange (probablemente otro pase allí) y luego otra pasada después de eso. Llevando a

ListView.InsertItems (desde la línea: 12952), segunda llamada (a través de EndUpdate) otro pase donde se agregan a un HashTabley un Debug.Assert(!listItemsTable.ContainsKey(ItemId)) lo ralentizará aún más en el modo de depuración. Si el identificador no se crea, agrega los elementos a un ArrayList, listItemsArray pero if (IsHandleCreated), luego llama

ListView.InsertItemsNative (de la línea: 3848) paso final a través de la lista donde en realidad se agrega a la vista de lista nativa. un Debug.Assert(this.Items.Contains(li) también ralentizará el rendimiento en el modo de depuración.

Así que hay MUCHOS pases extra a través de toda la lista de elementos en el control .net antes de que llegue a insertar realmente los elementos en la vista de lista nativa. Algunos de los pases están protegidos por controles contra el Mango que se está creando, por lo que si puede agregar elementos antes de que se cree el asa, puede ahorrarle algo de tiempo. los OnHandleCreatedmétodo toma el listItemsArray y llamadas InsertItemsNative directamente sin todo el alboroto extra.

Puedes leer el ListView código en el fuente de referencia usted mismo y echar un vistazo, tal vez me perdí algo.

En la edición de marzo de 2006 de MSDN Magazine hubo un artículo llamado Winning Forms: Practical Tips for Boosting The Performance of Windows Forms Apps.

Este artículo contiene consejos para mejorar el rendimiento de ListViews, entre otras cosas. Parece indicar que es más rápido agregar elementos antes de que se cree el identificador, pero que pagará un precio cuando se represente el control. Quizás aplicar las optimizaciones de representación mencionadas en los comentarios y agregar los elementos antes de que se cree el identificador obtendrá lo mejor de ambos mundos.

Edición: Probó esta hipótesis de varias maneras, y al agregar los elementos antes de crear el mango es suuupermente rápido, es exponencialmente más lento cuando va a crear el mango. Jugué tratando de engañarlo para crear el identificador, y de alguna manera hice que llamara a InsertItemsNative sin pasar por todos los pases adicionales, pero desafortunadamente me he visto frustrado. Lo único que podría pensar que podría ser posible es crear su Win32 ListView en un proyecto de C ++, rellenarlo con elementos y usar el enganche para capturar el mensaje CreateWindow enviado por el ListView al crear su identificador y devolver una referencia al win32 ListView en lugar de una nueva ventana ... pero quién sabe qué efecto tendría el lado ... sería necesario que un gurú de Win32 expresara su opinión sobre esa loca idea :)


21
2018-01-26 08:27



Usé este código:

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();

Yo también he establecido GenerateMember a falso para cada columna.

Enlace al clasificador de vista de lista personalizada: http://www.codeproject.com/Articles/5332/ListView-Column-Sorter


7
2017-07-07 22:16



Tengo el mismo problema. Entonces encontré que es sorter hazlo tan lento Hacer el clasificador como nulo

this.listViewAbnormalList.ListViewItemSorter = null;

luego cuando haga clic en Clasificador, en ListView_ColumnClick método, hazlo

 lv.ListViewItemSorter = new ListViewColumnSorter()

Por fin, después de haber sido ordenado, haz el sorter null de nuevo

 ((System.Windows.Forms.ListView)sender).Sort();
 lv.ListViewItemSorter = null;

0
2018-05-18 06:26



Crea todo tu ListViewItems  PRIMERO, luego agréguelos al Vista de la lista de repente.

Por ejemplo:

    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 );

-2
2018-01-27 22:30