lunes, 23 de junio de 2014

El Demo del Día: Comparación de Rendimiento al Llenar Listas en WinForms

Comparación de Rendimiento al Llenar Listas en WinForms

En este post, compararemos el rendimiento de diferentes técnicas para llenar listas en Windows Forms desde un arreglo. En WinForms los controles ListBox, ComboBox, CheckedListBox y ListView tienen una colección de Items que representan los elementos del control. Aprenderemos varias formas de mejorar la performance o rendimiento al llenar controles de tipo listas.

Problema al Llenar Listas en WinForms

Si tenemos un arreglo con datos y queremos llenar un control de tipo lista en WinForms, muchos programadores crean un bucle (for, foreach, while) y leen del arreglo cada elemento y lo agregan al control lista. Si son muchos registros a agregar el tiempo aumenta considerablemente en proporción a la cantidad de filas y se nota un parpadeo al momento de pintar el control.

Solución al Problema de Llenar Listas en WinForms

Existen 2 formas de solucionar el problema del rendimiento al llenar un control lista desde un arreglo:

1. Usar el método AddRange de la colección de Items del control lista y agregar directamente el arreglo.

2. Usar el método BeginUpdate antes del bucle para congelar la actualización en pantalla de fila x fila y al final del bucle llamar al método EndUpdate para actualizar el pintado del control lista.

Diseño de la Aplicación WinForms

Crear una aplicación WinForms llamada: "Comparacion_Rendimiento_Listas" en C# y cambiar de nombre al formulario por "frmLista", luego agregar los siguientes controles:
- Un Control Label llamado "lblTiempoProcesamiento"
- Un Control ListBox llamado "lstNumero"
- Cinco Controles Button llamados: "btnLlenarArreglo", "btnLlenarListaForItemsAdd",
  "btnLlenarListaAddRange", "btnLlenarListaForItemsAddBU" y "btnLlenarListaAddRangeBU".


Código Fuente de la Aplicación WinForms

En el código he creado un método "ejecutarMetodo" que tiene como parámetro un método gracias al tipo "Action" lo que me permite crear una rutina que permita "medir el tiempo de procesamiento de cualquier método" para poder comparar los tiempos en cada caso, para lo cual usamos la clase "Stopwatch" de "System.Diagnostics".
Además usamos un arreglo de cadenas, el cual lo llenamos con 10000 registros para poder ser pasados al control ListBox con las diferentes técnicas.

using System.Windows.Forms;
using System.Diagnostics;

namespace Comparacion_Rendimiento_Listas
{
    public partial class frmLista : Form
    {
        private string[] lista;

        public frmLista()
        {
            InitializeComponent();
        }

        public string ejecutarMetodo(Action metodo)
        {
            Stopwatch oReloj = new Stopwatch();
            oReloj.Start();
            metodo();
            oReloj.Stop();
            return (String.Format("Tiempo de Procesamiento: {0:n0} msg",
            oReloj.Elapsed.TotalMilliseconds));
        }

        private void llenarArreglo()
        {
            lista=new string[10000];
            for (int i = 0; i < lista.Length; i++)
            {
                lista[i] = i.ToString();
            }
        }

        private void btnLlenarArreglo_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarArreglo);
        }

        private void llenarListaForItemsAdd()
        {
            lstNumero.Items.Clear();
            for (int i = 0; i < lista.Length; i++)
            {
                lstNumero.Items.Add(lista[i]);
            }
        }

        private void btnLlenarListaForItemsAdd_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaForItemsAdd);
        }

        private void llenarListaAddRange()
        {
            lstNumero.Items.Clear();
            lstNumero.Items.AddRange(lista);
        }

        private void btnLlenarListaAddRange_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaAddRange);
        }

        private void llenarListaForItemsAddConBeginUpdate()
        {
            lstNumero.BeginUpdate();
            lstNumero.Items.Clear();
            for (int i = 0; i < lista.Length; i++)
            {
                lstNumero.Items.Add(lista[i]);
            }
            lstNumero.EndUpdate();
        }

        private void btnLlenarListaForItemsAddBU_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaForItemsAddConBeginUpdate);
        }

        private void llenarListaAddRangeConBeginUpdate()
        {
            lstNumero.BeginUpdate();
            lstNumero.Items.Clear();
            lstNumero.Items.AddRange(lista);
            lstNumero.EndUpdate();
        }

        private void btnLlenarListaAddRangeBU_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaAddRangeConBeginUpdate);
        }
    }
}

Ejecución y Pruebas de la Aplicación WinForms

Si ejecutamos la aplicación y damos clic al botón "Llenar Arreglo" notamos que el tiempo de procesamiento es de 2 msg (mili segundos), si damos clic al botón "Llenar Lista For Items Add" el tiempo de procesamiento es de 3306 msg (este puede variar cada vez que se da clic entre 2500 y 3500 aproximadamente), además en todo este tiempo se nota la aplicación en espera y con parpadeo.


Si damos clic al botón "Llenar Lista AddRange" o los otros 2 últimos botones (es decir las 3 formas de mejor rendimiento) notamos que el tiempo de procesamiento es de 69 msg (puede variar entre 60 y 80 msg).


Comentario Final

Después de hacer las comparaciones de rendimiento nos damos cuenta que la diferencia es extremadamente abismal, para 10000 filas el método clásico del bucle demora mas de 3000 msg mientras que las otras 3 técnicas solo 70 msg, es decir una diferencia de 3000, la cual aumenta con el aumento de los registros.

Después de esto, espero que ningún programador se olvide de colocar "BeginUpdate" antes del llenado de su lista y al final "EndUpdate" o use directamente el método AddRange.

Descarga:
Demo05_Rendimiento_Listas

6 comentarios:

  1. Excelente post profesor, valoro mucho este tipo de post que nos hagan programar de una manera que sea la mas optima posible!!, siga asi.

    ResponderBorrar
  2. ¡Gracias por el aporte Prof. Luis Dueñas!, pues muchos estudiantes e inexpertos como yo nos hemos centramos en el uso de técnicas "populares" que en su mayoría no son las mejores.
    Saludos.

    ResponderBorrar
  3. Gracias a ambos por sus comentarios, visiten seguido el sitio ya que habrán muchos posts sobre comparaciones de rendimiento (performance) en .NET, que no se ven en otros lugares.

    ResponderBorrar
  4. Este comentario ha sido eliminado por el autor.

    ResponderBorrar
  5. Definitivamente un buen aporte para ir mejorando el rendimiento en nuestro código

    ResponderBorrar