Mostrando entradas con la etiqueta Paralelismo. Mostrar todas las entradas
Mostrando entradas con la etiqueta Paralelismo. Mostrar todas las entradas

domingo, 8 de febrero de 2015

!Apunte semanal - Problemas al depurar tareas en paralelo?


Hola amigos, en anteriores artículos tuvimos una introducción al paralelismo, y mucho más enfocado en lo que Microsoft .Net nos ofrece para trabajar con él, en resumidas cuentas tenemos la TPL que es la librería que nos permite trabajar con paralelismo de una forma muy sencilla, y que nos ofrece varias formas de trabajo como lo son el trabajo con datos a través de ciclos paralelos, paralelismo a través de tareas (Task), Plinq que nos permite realizar consultas linq en paralelo, entre otras. Y entre estas formas de trabajo hay una en particular que se convierte en transversal para todas, y es la depuración de procesos en paralelo, de suma importancia y utilidad para llevar un paso a paso de las instrucciones en las cuales estemos empleando el paralelismo, ya que en este tipo de instrucciones en paralelo podemos tener problemas a la hora de depurar, ya que cuando estamos haciendo paso a paso de una instrucción en ocasiones el depurador se salta y va a otra instrucción que se está ejecutando en simultánea, y luego vuelve a anterior, con lo cual tenemos un gran problema para comprender y seguir la depuración de una tarea en específico, yo por ejemplo he tenido este problema cuando trabajo referenciando dlls de otros proyectos de los cuales no tengo los proyectos agregados en la solución que estoy depurando.


Pues bueno para solucionar este problema si es que se nos presenta, podemos usar la clase Debugger del NameSpace System.Diagnostics, la cual nos permitirá hacer un punto de ruptura a través del método Break(), veamos cómo lo podemos hacer:

Primero vamos a crear en una aplicación de consola con una ejecución de tareas que invoquen diferentes métodos, tal como vemos a continuación:

        static void Main(string[] args)
        {
            Task t = new Task(TareaA);
            t.Start();
 
            Task t2 = new Task(TareaB);
            t2.Start();
 
            Task t3 = new Task(TareaC);
            t3.Start();
 
            Task.WaitAll(new Task[] { t, t2, t3 });
            Console.Write("Terminó la ejecución de las tareas A, B y C.");
        }

 Y ahora vamos usar un punto de ruptura en alguna de las tareas:

        private static void TareaA()
        {
            Debugger.Break();
            Thread.Sleep(2000);
            Console.Write("Se ejecutó la tárea A");
        }

Con este Debugger.Break() ahora podemos depurar el contenido de la tarea A, sin tener problemas de con las otras tareas que se ejecutan en paralelo, adicional también nos podría ser de utilidad cuando tenemos instrucciones muy tediosas de depurar, por ejemplo si estamos recorriendo una lista de 10000 ítems y tenemos un error que solo se genera con alguno de los ítems, podríamos realizar una condicional y usar el Debugger.Break para capturar con mayor facilidad el problema (aunque también lo podríamos hacer con las condicionales que nos ofrecen los puntos de interrupción de Visual Studio).

Bueno y eso es todo de este apunte semanal, espero sea de utilidad.

Saludos y buena suerte!

lunes, 10 de junio de 2013

Programación paralela a través de tareas (Task) en .NET

En muchas ocasiones se nos presentan escenarios en los cuales debemos ejecutar procesos de forma paralela, debido a que de forma secuencial se vuelven muy lentos y bloquean al usuario por un tiempo considerable, uno de estos escenarios puede ser si en una aplicación de reservas de vuelos online, debemos buscar disponibilidad mediante web services tanto para la aerolínea A como para la aerolínea B, si lo ejecutamos de forma secuencial iniciamos el llamado a A, y cuando este termine iniciamos el llamado a B, esto tardaría un tiempo considerable, la mejor forma sería ejecutando los llamados al mismo tiempo, usando Multithreading o usando paralelismo, en nuestro ejemplo vamos a usar paralelismo ya que vamos a usar todos los núcleos de nuestro procesador.

Para tener conceptos claros acerca de la programación paralela, recomiendo leer mi artículo anterior "Introducción a la programación paralela"

http://eltavodev.blogspot.com/2013/05/introduccion-la-programacion-paralela.html

Ahora vamos a ver ejemplos de cómo usar paralelismo a través de tareas en .NET, crearemos la siguiente aplicación:


A lo largo del ejemplo vamos a usar tres métodos: TareaA(), TareaB, TareaC(), tarea A, tiene un tiempo de retardo para efectos del ejercicio:

private void TareaA()
{
      Thread.Sleep(2000);
      MessageBox.Show("Se ejecutó la tárea A");
}

En nuestro primer botón "Task", vamos a crear una tarea indicándole que debe ejecutar nuestro método TareaA, e iniciamos la tarea:

Task t = new Task(this.TareaA);
t.Start();

También podemos crear tareas que ejecuten múltiples instrucciones:

Task t = Task.Factory.StartNew(() =>
{
      this.TareaA();
      int resultado = 1 + 2;
});
this.TareaB();

También podemos tipar nuestras tareas:

Task<int> t = Task.Factory.StartNew(() =>
{
      return 1 + 2;
});
MessageBox.Show((t.Result + 1).ToString());

Si queremos especificar una secuencia de ejecución para nuestras tareas, lo podemos hacer mediante la instrucción ContinueWith:

Task.Factory.StartNew(() =>
{
      this.TareaA();
}).ContinueWith((t) => TareaB());

Y por último vamos ver como sincronizar el fin de ejecución de todas nuestras tareas, muy útil en caso de que necesitemos ejecutar algún proceso cuando terminen todas las tareas que tenemos ejecutándose en paralelo:

Task t = new Task(this.TareaA);
t.Start();
 
Task t2 = new Task(this.TareaB);
t2.Start();
 
Task t3 = new Task(this.TareaC);
t3.Start();
 
Task.WaitAll(new Task[] { t, t2, t3 });
 
MessageBox.Show("Se terminó la ejecución de todas las táreas");

Con esto damos por terminado nuestro artículo sobre programación paralela a través de tareas, espero les sea de gran utilidad, a continuación el link de descarga del código de ejemplo:

https://www.dropbox.com/s/gsjhi12w68r1wuq/EjemploPTask.rar

Saludos.

domingo, 26 de mayo de 2013

Consultas Linq to Objects en paralelo con Plinq

En este artículo vamos a ver como optimizar nuestras consultas linq to objects usando todos los núcleos de nuestro procesador a través de Plinq.

Para tener conceptos claros acerca de la programación paralela, recomiendo leer mi artículo anterior “Introducción a la programación paralela”



¿Qué es Plinq?

Parallel LINQ (PLINQ) es una implementación paralela de LINQ to Objects. PLINQ implementa el conjunto completo de operadores de consulta estándar de LINQ como métodos de extensión para el espacio de nombres T:System.Linq y tiene operadores adicionales para las operaciones paralelas.

Para entrar en materia vamos a ver ejemplos de consultas linq to objects y consultas Plinq y vamos a comparar el tiempo que tarda cada una, para eso implementaremos el siguiente formulario:



Para nuestras consultas vamos a declarar una enumeración de 1 a 10’000.000, un Stopwatch para medir el tiempo que tardan las consultas y vamos a crear un método que busque los números primos en el rango anterior con el fin de darle complejidad a las consultas para que tarden más tiempo para efectos de nuestro ejemplo.

        private bool esPRimo(int n)
        {
            if (n <= 1) return false;
            if ((n & 1) == 0)
            {
                if (n == 2) return true;
                else return false;
            }
            for (int i = 3; (i * i) <= n; i += 2)
            {
                if ((n % i) == 0) return false;
            }
            return n != 1;
        }



        IEnumerable<int> numeros = Enumerable.Range(1, 10000000);
        Stopwatch tiempo = new Stopwatch();


En el evento click de nuestro primer botón “Linq” vamos a copiar el siguiente fragmento de código para realizar la consulta a través de linq to objects y mostrar el tiempo transcurrido:


            tiempo.Restart();
            var con = numeros.Where(c => esPRimo(c));
            con.ToList();
            tiempo.Stop();
            this.lblTiempo.Text = string.Format("Tiempo transcurrido: {0}",  tiempo.ElapsedMilliseconds.ToString("n2"));

Esta consulta tarda 5.715 milisegundos, una diferencia considerable con la siguiente implementación en paralelo que tarda 2.909 milisegundos


            tiempo.Restart();
            var con = numeros.AsParallel().Where(c => esPRimo(c));
            con.ToList();
            tiempo.Stop();
            this.lblTiempo.Text = string.Format("Tiempo transcurrido: {0}", tiempo.ElapsedMilliseconds.ToString("n2"));

Nótese que solo tuvimos que usar la extensión AsParallel para usar los demás cores del procesador, observemos el comportamiento de nuestro procesador con el administrador de tareas:

Imagen de consulta linq to objects:



Imagen de consulta Plinq:


Podemos observar como en la consulta de linq to objects se dispara el uso de un solo núcleo del procesador, mientras que en la consulta Plinq se dispara el uso de todos los núcleos, he ahí la gran diferencia en tiempo.

Algo a tener en cuenta es que nuestra consulta Plinq no devolverá los resultados en orden, ya que no procesa de forma secuencial, en caso de que necesitemos obtener en orden los números como es el caso de nuestro ejemplo solo basta con hacer uso de la extensión AsOrdered, como se muestra a continuación:

var con = numeros.AsParallel().AsOrdered().Where(c => esPRimo(c));



Obviamente eso hace que tarde un poco más nuestra consulta pero no es mucho, solo aproximadamente un milisegundo.

Para terminar nuestro ejemplo hay un aspecto que es importante resaltar y es la especificación del grado de paralelismo que deseamos utilizar en nuestras consultas y lo logramos con la extensión WithDegreeOfParallelism(Environment.ProcessorCount / 2) y especificamos el número de núcleos que queremos usar.


            tiempo.Restart();
            var con = numeros.AsParallel().WithDegreeOfParallelism(Environment.ProcessorCount / 2).Where(c => esPRimo(c));
            con.ToList();
            tiempo.Stop();
            this.lblTiempo.Text = string.Format("Tiempo transcurrido: {0}", tiempo.ElapsedMilliseconds.ToString("n2"));

En el ejemplo anterior usamos la mitad de los núcleos que tiene el procesador.

Con esto damos por terminado nuestro artículo sobre Plinq, espero les sea de gran utilidad, en un próximo artículo veremos otra forma de paralelismo en .NET como lo es el paralelismo mediante Tareas.

A continuación el link de descarga del ejemplo.

https://www.dropbox.com/s/ogk7skbwfmt5qcf/EjemploPlinq.rar

Saludos.

sábado, 25 de mayo de 2013

Introducción a la programación paralela

En este artículo vamos a dar un vistazo acerca de la programación paralela, para que nos sirve, conceptos importantes y que nos ofrece Microsoft .NET para implementarla, veamos:

¿Para qué nos sirve?  
  • La programación paralela nos sirve para ejecutar varias tareas de forma simultanea.
  • Se rige bajo el principio de que problemas grandes se pueden dividir en unos más pequeños, para ser resueltos simultáneamente.
  • Existen los siguientes tipos de paralelismo: a nivel de bit, instrucción, datos y tareas.     
El concepto de paralelismo toma fuerza, debido a que los procesadores en la actualidad buscan tener más núcleos para desempeñarse mejor, y no tener más velocidad con un solo núcleo debido a temas de recalentamiento.

Conceptos importantes:
  • Proceso: Proporciona los recursos necesarios para ejecutar un programa, se inicia con un código único de identificación y ocupa un espacio en memoria virtual, requiere al menos un hilo para ejecutarse. 
  • Hilos (Threads): Entidad dentro de un proceso que realmente ejecuta código. Todos los hilos comparten los recursos y memoria virtual del proceso donde se ejecutan.
Consideraciones:
  • A mayor cantidad de hilos, mayor uso de CPU, ya que el sistema operativo sede el recurso basado en el número de hilos. 
  • Las aplicaciones .NET usan al menos dos hilos para ejecutarse, uno para ejecutar su código y otro para el Collector. 
  • Cuando un proceso tiene muchos hilos consume mucho más tiempo de CPU que los demás y funciona bastante rápido, pero los demás procesos deben esperar más.
Diferenciemos dos conceptos que suelen confundirse como lo son Multithreading y paralelismo.

Multithreading v.s paralelismo
  • Multithreading Permite manejar varios hilos en una aplicación. 
  • Permite realizar tareas asíncronas, pero usa un solo core del procesador. 
  • Es complejo para controlar, difícil de depurar y no permite acceso a UI.
  • Paralelismo Permite implementar multithreading usando varios core del procesador. 
  • Permite un fácil manejo y buena depuración. 
  • Permite acceso a UI. 
  • Permite especificar cuantos core del procesador usar para un tarea en especifico.
¿Qué nos ofrece .NET Framework para trabajar paralelismo?
  • PLinq: Consultas Linq to Objects con paralelismo.
  • Data Parallel: Paralelismo con bucles.
  • Task: Agrupa instrucciones para ser ejecutas en paralelo.
  • Concurrent: Maneja sincronización en colecciones.
  • Depuración: Permite depurar diferentes hilos ejecutándose en paralelo. 
Esto es una breve introducción a la programación paralela, en próximos artículos veremos como implementar paralelismo en .NET, espero les sea de utilidad.