Una colección que implementa la interfaz IEnumerable contiene un método GetEnumerator y este a su vez implementa la interfaz IEnumerator que contiene la propiedad Current y los métodos MoveNext y Reset. Esto va a permitir a la instrucción foreach iterar por la colección.

Así, si generamos una colección de 20.000.000 de números enteros y queremos efectuar una operación por cada uno de ellos, en este caso el inverso del producto de la raiz cuadrada por el logaritmo neperiano y luego realizar la suma sobre cada resultado, podemos iterar con una instrucción foreach,

static int n = 20000000;
static List list = Enumerable.Range(1, n).ToList();

static double forEachStandar()
        {
            var result = 0.0;

            foreach (var item in list)
            {
                result += 1 / Math.Sqrt(item) * Math.Log(item);
            }
            return result;
        }

Una colección además contiene un método ForEach, el cual es accesible mediante una expresión Lambda, asignando la acción que vamos a realizar en cada uno de los elementos.

        static double forEachLambda()
        {
            var result = 0.0;
            list.ForEach(n => result += 1 / Math.Sqrt(n) * Math.Log(n));
            return result;
        }

Podemos aprovechar al máximo los recursos disponibles y es iterando en paralelo por los elementos de la colección pudiendo realizar cálculos simultáneos. Este medio debe ser tratado con cuidado, puesto que si el orden interviene en el resultado final, esta herramienta no sería útil, y en todo caso, los resultados no serían los esperados; así mismo, siempre hay que replantearse el uso en paralelo. Para usarlo, podemos hacerlo mediante la clase Parallel
Parallel.ForEach(list, k => result += 1 / Math.Sqrt(n) * Math.Log(n));
o mediante el método AsParallel de la colección.

foreach (var item in list.AsParallel())
{
result += 1 / Math.Sqrt(item) * Math.Log(item);
}

Aprovechando el artículo sobre LINQ publicado en Wakicode, es posible efectuar cálculos sobre elementos de la colección mediante sintáxis de consulta en Linq. En el siquiente ejemplo, efectuamos el cálculo sobre cada uno de los elementos asignádolo a la variable sum y posteriormente efectuar la suma por cada uno de los elementos

static double queryLinq()
        {
            var query = (from u in list
                        select new { sum = 1 / Math.Sqrt(u) * Math.Log(u) }).Sum(k=>k.sum);

            return query;
        }

pero existe un método mejor y es que toda colección contiene el método Sum, y podremos entonces hacer lo siguiente

        static double collectionSum()
        {
            return list.Sum(k => 1 / Math.Sqrt(k) * Math.Log(k));
        }

Bien, todavía habría alguna otra forma de iterar sobre colecciones, pero con estas nos basta y ahora vamos a ver como se comportan en base a su rendimiento y para ello he creado cuatro hilos, de los cuales cada uno generará la suma de los elementos calculados de la colección, pero cada uno con su método y una vez que terminen todos, que nos muestre el tiempo invertido en los cuatro procesos y cual ha sido el más eficiente. Lanzaremos los procesos por este orden, foreach estándar, expresión foreach lambda, Linq sintáxis de consulta y por último la suma directa sobre la colección mediante expresión lambda.

        static void inBackGround()
        {
            string text = "";
            List cronos = new List();

            Stopwatch stw = new Stopwatch();
            Stopwatch stw1 = new Stopwatch();
            Stopwatch stw2 = new Stopwatch();
            Stopwatch stw3 = new Stopwatch();
            Stopwatch stw4 = new Stopwatch();

            cronos.Add(new KeyValuePair(stw1, "For Each Estandar"));
            cronos.Add(new KeyValuePair(stw2, "For Each Lambda"));
            cronos.Add(new KeyValuePair(stw2, "Query LINQ"));
            cronos.Add(new KeyValuePair(stw2, "Colección.Sum()"));

            stw.Start();
            Task[] tasks;

            var t1 = Task.Factory.StartNew(() =>
            {
                stw1.Start();
                text = string.Format("For Each Estandar. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachStandar(), stw1.Elapsed);
                stw1.Stop();
                Console.WriteLine(text);
            });

            var t2 = Task.Factory.StartNew(() =>
            {
                stw2.Start();
                text = string.Format("For Each Lambda. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, forEachLambda(), stw2.Elapsed);
                stw2.Stop();
                Console.WriteLine(text);
            });

            var t3 = Task.Factory.StartNew(() =>
            {
                stw3.Start();
                text = string.Format("Query LINQ. n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, queryLinq(), stw3.Elapsed);
                stw3.Stop();
                Console.WriteLine(text);
            });

            var t4 = Task.Factory.StartNew(() =>
            {
                stw4.Start();
                text = string.Format("Colección.Sum(). n = {0}. Resultado = {1}. Tiempo del proceso: {2}", n, collectionSum(), stw4.Elapsed);
                stw4.Stop();
                Console.WriteLine(text);
            });

            tasks = new Task[] { t1, t2, t3, t4 };

            Task.WaitAll(tasks);
            Console.WriteLine(string.Format("Los cuatro procesos han terminado. Tiempo: {0}",stw.Elapsed));
            var theBest = cronos.Where(k => k.Key.Elapsed == cronos.Min(m => m.Key.Elapsed)).FirstOrDefault();
            Console.WriteLine(string.Format("El más efectivo ha sido {0} con un tiempo de {1}",theBest.Value, theBest.Key.Elapsed));

            Console.Read();

        }

y ahora el resultado en el que el ganador en cuanto a rendimiento es el método For Each Lambda por muy poco sobre la suma sobre la colección, el foreach estándar y el menos eficiente la consulta Linq.

foreach.png
El fin del artículo, para un problema podemos usar distintos métodos que producen el mismo efecto o resultado, pero debemos escoger el mejor de ellos en cuanto a eficiencia y rendimiento. Además, necesitamos conocer todos los métodos, no podemos escoger o seleccionar si no tenemos o no conocemos opciones.
Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s