none
¿Como mejorar el rendimiento al pasar de un IEnumerable<T> a un List<T>? RRS feed

  • Pregunta

  • En las ultimas semanas hemos tenido problemas de rendimiento en nuestra aplicacion en cuestion de tiempo de consulta y visualizacion de informacion.

    Revisando a fondo el asunto nos percatamos que la consulta a la base de datos no tardaba nada (ni un milisegundo) asi fueran millones de registros, el problema radicaba cuando al resultado de la consulta le aplicabamos el .ToList().

    Nos percatamos que el tiempo se extendia de sobremanera. Por ejemplo al consultar un total de 4600 registros la consulta dilataba en promedio 00:00:00.0001523 pero al convertirlo en .ToList() el tiempo se alargaba a 00:00:04.3138323.

    Ahora bien tal vez no sea la gran cosa, pero me he topado con la consulta de 2 000 000 de registros sin ocupar un .ToList() solo al ocupar un foreach mi tiempo se traduce a aprox 4 min.

    Pense que era por el volumen de registros, pero ¿Es normal que un IEnumerable de 50 items tarde aprox 2 min?

    Mi pregunta es ¿Hay alguna forma de mejorar el rendimiento al hacer esta conversion? o ¿Hay alguna forma de recorrer un IEnumerable de forma mas eficiente?

    Agradezco de antemano la ayuda que puedan proporcionar.

    jueves, 22 de octubre de 2015 0:39

Respuestas

  • Hola a todos, ya encontré parte de la solución y se las comparto por si alguien la requiriese:

    Como les mencionaba no estamos manejando la bdd de SQL si no una basada en Sybase, de modo que la solución fue indexar el campo de nuestra tabla que estaba fungiendo como filtro principal, en este caso la fecha, ya que la consulta se hacia en base  a un rango de fechas.

    Después de indexar dicho campo, el foreach fluyo rápidamente.

    Desconozco a ciencia cierta cual es realmente la diferencia, pero me atrevo a decir que tiene que ver con lo que mencionaban de la memoria caché.

    Saludos.

    • Marcado como respuesta vivispato lunes, 26 de octubre de 2015 16:21
    lunes, 26 de octubre de 2015 16:21

Todas las respuestas

  • ¿Hay alguna forma de recorrer un IEnumerable de forma mas eficiente?


    La interfaz IEnumerable solo expone un único método, llamado GetEnumerator. Y este método devuelve un IEnumerator, que expone tres métodos: Reset, MoveNext y Current. La UNICA manera de recorrer un IEnumerable es llamar repetidamente a MoveNext para que avance a la siguiente posición y luego llamar a Current para que te entregue el elemento posicionado. Esto es precisamente lo que hace internamente el compilador cuando escribes un foreach, así que no hay ninguna manera de mejorarlo. Si resulta lento, la única posible causa es porque sea lenta la implementación interna de dichos métodos dentro del objeto que te está proporcionando el IEnumerable. Para aumentar la velocidad hay que sustituir dicho objeto por otro más eficiente, pero no se puede mejorar desde fuera del IEnumerable.
    jueves, 22 de octubre de 2015 5:35
  • De acuerdo con lo que dice Alberto.

    Además no me parece normal que materializar 50 items tarde 2 minutos.

    Una cosa que puede ayudarte es intentar aplicar la mayor cantidad de filtros en la consulta antes de materializar (aplicar un ToList implica materializar, traerse los objetos a memoria desde disco). Eso si el número de objetos es grande es muy costoso.

    Si van a realizar una tarea de iteración es mejor no aplicar el ToList e iterar sobre el IQueryable, que irá haciendo las llamadas y la materialización de la manera más eficiente. Si hay que devolver el conjunto entero de datos a cascoporro, ahí la cosa se complica, o lo paginas (en caso que el destino sea una UI) o teneés que lidiar con el coste de la meterialización.

    Si nos mostrás parte del código seguro te podemos echar un mano.

    PD: la consulta SQL tarda poco en ejecutarse, seguramente. Si le pedís todos los datos para mostrarlos por pantalla seguro que tarda algo más.

    Salu2

    jueves, 22 de octubre de 2015 7:19
  • Gracias Alberto y Matias por responder tan pronto a mi pregunta:

    Alberto ayer antes de salir del trabajo lei sobre lo que mencionas, asi que me pondre a trabajar sobre ello y ver si eso mejora en algo el tiempo.

    Matias, te comparto un poco de lo que estoy haciendo, que es muy parecido a todo lo que mencionas:

    En mi capa de datos tengo mi consulta a la bdd que no es SQL, es Advantage Database Server:

    public IEnumerable<clsKARDEXTIPOMOVIMIENTOADS> GenerarKardexTipoMovimientoNew(string tipoMovimiento, DateTime fechaInicial, DateTime fechaFinal)
            {
                try
                {
                    return (from m in contexto.MOVIMIENTOS
                            orderby m.ID_MOVIMIENTOS
                            where
                                m.TIPO_MOVIM.Equals(tipoMovimiento) &&
                                (m.F_MOVIM >= fechaInicial && m.F_MOVIM <= fechaFinal)
                            select new clsKARDEXTIPOMOVIMIENTOADS()
                            {
                                IdMovimiento = m.ID_MOVIMIENTOS,
                                Clave = m.CLAVE_ART,
                                EntradaSalida = m.ENT_SAL,
                                FechaMovimiento = m.F_MOVIM,
                                TipoMovimiento = m.TIPO_MOVIM,
                                Referencia = m.NO_REFEREN,
                                Almacen = m.ALMACEN,
                                Cantidad = m.CANTIDAD,
                                Costo = m.COSTO,
                                Usuario = m.CLAVE_USR
                            }); 
                }
                catch (Exception)
                {
                    throw;
                }
            }


    En la capa logica solo hace la union de mi consulta con la presentacion en Windows Forms

    public IEnumerable<clsKARDEXTIPOMOVIMIENTOADS> GenerarKardexTipoMovimientoNew(string tipoMovimiento, DateTime fechaInicial, DateTime fechaFinal)
            {
                try
                {
                    return contexto.GenerarKardexTipoMovimientoNew(tipoMovimiento, fechaInicial, fechaFinal);
                }
                catch (Exception)
                {
                    throw;
                }
            }

    Por ultimo ya en la presentacion hago varios filtros de ese IEnumerable, en base a condiciones de mi formulario, hasta este punto los sigo manejando como un IEnumerable.

    Llegados a este punto el tiempo se traduce un un parpadeo osea nada!!!!! Pero......

    Cuando yo intento hacer un foreach de este, dilata en pasar por el primer item (dilata aprox. 1 min), despues de ello trabaja rapidamente.

    foreach (var movADS in listaMovimientosADS)
    {
    ...
    }

    Si este IEnumerable final lo materializo con un .ToList(), esta conversión es la que dilata varios minutos asi sean 50 items,  y no te quiero comentar cuando son miles.... Pero cuando pasa por el foreach es mas rápido.

    var lista  = listaMovimientosADS.ToList();
    foreach (var movADS in listaMovimientosADS)
    {
       ...
    }

    ---------------------------

    Bueno tal vez se preguntaran por que no lo regreso como una lista desde el principio, pues la razón es que me marca un error diciendo que el tiempo de consulta se agoto. T.T.

    De todas formas voy a probar la recomendacion de Alberto, y si tienen alguna otra idea pues bienvenida sea 

    Gracias!

    jueves, 22 de octubre de 2015 14:44
  • Hola a todos, ya encontré parte de la solución y se las comparto por si alguien la requiriese:

    Como les mencionaba no estamos manejando la bdd de SQL si no una basada en Sybase, de modo que la solución fue indexar el campo de nuestra tabla que estaba fungiendo como filtro principal, en este caso la fecha, ya que la consulta se hacia en base  a un rango de fechas.

    Después de indexar dicho campo, el foreach fluyo rápidamente.

    Desconozco a ciencia cierta cual es realmente la diferencia, pero me atrevo a decir que tiene que ver con lo que mencionaban de la memoria caché.

    Saludos.

    • Marcado como respuesta vivispato lunes, 26 de octubre de 2015 16:21
    lunes, 26 de octubre de 2015 16:21