2012-07-02 12 views
5

Recientemente inicié una aplicación WPF. Lo conecté a una base de datos BaseX (basada en XML) y recuperé aproximadamente un millón de entradas de él. Quería para repetir las entradas, calcular algo para cada entrada y luego escribir que de vuelta a la base de datos:Iterar colecciones grandes en C#: Tomar mucho tiempo

IEnumerable<Result> resultSet = baseXClient.Query("...", "database"); 
foreach (Result result in resultSet) 
{ 
    ... 
} 

El problema: no se alcanza nunca el interior del foreach. el método Query() regresa bastante rápido, pero cuando se alcanza el foreach C# parece hacer ALGO con la colección, el código no continúa durante un tiempo muy largo (al menos 10 minutos, nunca lo deje funcionar). ¿Qué está pasando aquí? Traté de limitar el número de elementos recuperados. Al recuperar 100.000 resultados, ocurre lo mismo, pero el código continúa después de unos 10-20 segundos. Al recuperar el millón de resultados completos, C# parece estar estancado para siempre ...

¿Alguna idea? respecto

Editar: por qué ocurre esto Como algunos de ustedes han señalado, la razón de este comportamiento parece ser que la consulta es en realidad sólo se evalúa cuando MoveNext() en el interior del enumerador Enumerable se llama. Parece que mi base de datos no puede devolver un valor a la vez, sino que devuelve todo el millón de conjunto de datos a la vez. Intentaré cambiar a otra base de datos (Apache Lucene, si es posible, ya que tiene un buen soporte de búsqueda de texto completo) y editar esta publicación para hacerle saber si cambió algo.
PD: Sí, soy consciente de que un millón de resultados es mucho. Esto no es para uso en vivo, es solo un paso para preparar los datos. Si bien no esperaba que el código se ejecutara en unos pocos segundos, todavía me sorprendió ver TAL rendimiento deficiente en la base de datos.

Edit: The Solution Así que migré la base de datos XML a Apache Lucine. ¡Funciona de maravilla! Por supuesto, Lucine es una base de datos basada en texto que no es adecuada para todos los casos de uso, pero para mí funcionó de maravilla. Puede iterar más de un millón de entradas en unos pocos segundos, se obtiene una entrada por ciclo: ¡funciona extremadamente bien!

+7

[ejecución diferida] (http://blogs.msdn.com/b/charlie/archive/2007/12/09/deferred-execution.aspx). – Adam

+0

@codesparkle, buena información, pero me pregunto por qué parece que toda su consulta debe ejecutarse antes de que se devuelva el primer elemento. –

+1

Considere publicar más código y una muestra menor de los datos. – EKS

Respuesta

3

Un millón de cualquier cosa es mucho ... por lo tanto, se espera que cualquier operación que obtenga tantos elementos lleve bastante tiempo. Parece que la biblioteca que utiliza no pospone la recuperación de elementos hasta que sea absolutamente necesario, por lo que puede ver el impacto de obtener todos los elementos ocultos detrás de la declaración "foreach".

Lo que sucede:

"foreach" no es una sola operación, sino varias llamadas en IEnumerable y IEnumerator: IEnumerable.GetEnumerator, repitieron las llamadas a IEnumerator.MoveNext.

Primera llamada GetEnumerator puede implementarse con ejecución diferida (forma más común de cómo se escriben las consultas LINQ) o la ejecución inmediata (que parece ser el caso de su colección.

llamadas a MoveNext también podrían activar la ejecución inmediata de toda la consulta incluso si está preguntando solo por un elemento o cada llamada puede obtener un solo elemento. Es decir, la mayoría de las consultas LINQ obtienen solo un siguiente elemento del iterador.

+0

Gracias por la información completa. Parece que tienes razón. Si utilizo MoveNext() manualmente, aquí es donde todo se bloquea. Parece que la base de datos no permite obtener solo un elemento, una pena. Intentaré con otra base de datos e informaré en mi publicación original. – BlackWolf

-1

¿Has probado un lazo más tradicional para ver si eso funcionaría?

IEnumerable<Result> resultSet = baseXClient.Query("...", "database"); 
for (int x =0; x < resultSet.Count; x++) 
{ 
    Result result = resultSet[x]; 
    ... 
} 
+1

La ejecución diferida es lo que está sucediendo aquí; esto no cambiará eso. Además de eso, esto no se compilará ya que un 'IEnumerable' no tiene una propiedad' Count', o un indexer. – Servy

+1

a la derecha. Count (después de usar ToList()) o MoveNext() en el Enumerator bloquean la ejecución, por lo que parece no haber forma de evitarlo. – BlackWolf

5

Let me quess - no va a cargar los datos cuando youcreate la rsultset, pero cuando se accede por primera vez (ejecución retardada), y la carga de un millón de entradas sólo se toman mucho tiempo para deserializar ellos en la memoria .

Bienvenido a las ineficiencias de las bases de datos XML.

+1

Bueno, se dijo que BaseX sería bastante rápido, pero intentaré tomar los datos en una base de datos Apache Lucene hoy y ver si eso acelera las cosas. Tienes razón de que un millón es mucho, y no espero que se devuelvan en segundos, pero tampoco espero que tomen más de 10 minutos. – BlackWolf

+0

Odio decírtelo, pero ese es un requisito muy duro. Seriamente. 1ms por artículo sale a 1000 segundos, que ES 10 segundos. 1ms no es mucho para "xml", que es notoriamente ineficiente de tratar para empezar, por lo que dependiendo del tamaño del XML, puede tomar más tiempo. – TomTom

0

Para forzar que se evalúe la consulta antes del foreach, llame a la función ToList en su resultSet. (no resolverá su problema si el problema es que la base de datos lo tome para siempre)

+1

Llamar a 'ToList',' ToArray' o 'ToDictionary' tiene el mismo efecto. –

+0

Llamar a 'Count' en efecto lo evaluará, pero si luego 'foreach' sobre él, entonces está obteniendo un nuevo' IEnumerator' que necesita ser evaluado, lo que significa que ahora está haciendo la consulta dos veces. Si simplemente 'ToList' /' ToArray'/etc the data y luego 'foreach' lo buscará ansiosamente los datos y solo los buscará una vez. – Servy

2

Las respuestas aquí apuntan a la causa de su problema percibido con foreach (ejecución diferida), pero no a una posible solución. No estoy seguro de si esta base de datos lo admite, pero una solución podría ser intentar paginar la res en lotes más pequeños, en lugar de obtener toda la información a la vez.

Una alternativa es escribir una consulta de base de datos que realice los cálculos necesarios, de modo que su base de datos nunca tenga que enviar 1 millón de registros a ningún lado. (Nuevamente, no estoy seguro si esta base de datos lo admite)

+1

Gracias. Tienes razón, probablemente haya soluciones más sofisticadas para lo que estoy haciendo aquí. Aún así, me sorprende que la base de datos tarde tanto en recuperar los elementos. Desafortunadamente, puedo obtener los datos en lotes, pero dado que tengo que realizar una consulta de actualización para cada conjunto de datos, que desafortunadamente no se puede paralelizar, eso no ayudará mucho. – BlackWolf

Cuestiones relacionadas