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.
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:
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:
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.
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.