Después de tener algo de tiempo he ido a mi lista de cosas por leer y me quedado a cuadros cuando lo he leído.
http://blogs.msdn.com/b/etayrien/archive/2007/03/17/foreach-garbage-and-the-clr-profiler.aspx
Sí es un enlace del 2007, lo sé, imaginaos la de cosas que tengo en esa lista :P.
En resumen, imaginemos este código:
class Program { class GameEntity { public void Update() { } } <pre><code>static GameEntity[] entities = new GameEntity[100]; static Program() { for (int i = 0; i &lt; entities.Length; i++) { entities[i] = new GameEntity(); } } static void Main(string[] args) { byte[] byteArray = new byte[1]; for (int i = 0; i &lt; entities.Length; i++) { entities[i].Update(); } } </code></pre> }
En el post original, después de pasar el CLR Profiler no se hace reserva de memoria para un enumerador para poder recorrer la colección, algo lógico. Después, para ver la diferencia con el foreach sustituye el código del for por este otro:
static void Main(string[] args) { byte[] byteArray = new byte[1]; foreach (GameEntity e in entities) { e.Update(); } }
En el caso del foreach se reserva memoria para un enumerador necesario para recorrer el foreach.
Y todo va perfecto, sin embargo, hay un escenario en el que se pueden producir fugas de memoria:
const int NumEntities = 100; static List list = new List(); static Program() { for (int i = 0; i < NumEntities; i++) { list.Add(new GameEntity()); } } static void Main(string[] args) { UpdateIEnumerable(list); } private static void UpdateIEnumerable(IEnumerable enumerable) { foreach (GameEntity e in enumerable) { e.Update(); } }
En este caso sí se producen fugas de memoria. Y es que aunque estemos haciendo un foreach en una lista, cuando se le hace un casting a una interfaz, al tipo valor del enumerador se le hace un box, y se coloca en el heap.
La conclusión:
- Cuando hacemos un foreach sobre un Collection<T> se reserva memoria para un enumerador.
- Cuando hacemos un foreach sobre la mayoría de las colecciones, como arrays, listas, colas, listas enlazadas y otras:
- Si se usan explícitamente, NO se reserva memoria para un enumerador.
- Si se usan a través de interfaces, se reserva memoria para un enumerador.
Así que si el consumo de memoria es algo crítico en vuestra aplicación o juego , nunca, nunca, uséis interfaces para recorrer una colección.
[Update: Gracias a Bernardo por el comentario]
El problema aparece cuando el “foreach” recorre la colección como si fuera un “IEnumerable”. En este caso se utiliza la implementación explícita de “GetEnumerator()” y se realiza “boxing” del “struct” para devolver un tipo “IEnumerator”.
El “boxing” es una operación costosa y puede llegar a consumir mucha memoria.
P.D: el método “GetEnumerator()” de “Collection” no devuelve un “struct”. Es de las pocas colecciones que son una excepción.
Espero que os haya gustado tanto como a mi. 🙂
Juan María Laó Ramos.