CrAzY hOuSe
Locos por la programación
Inicios con Parallel Extensions
.net

Syndication

Debido a la proliferación de procesadores de núcleos múltiples, los picatecla desarrolladores tenemos que plantearnos seriamente el cambio de “mentalidad” para aprovechar todo el potencial de estos nuevos ordenadores.

Cualquiera que haya trabajado con Thread recordará que no era nada sencillo asignar diferentes hilos a algún proceso, recoger el resultado, mezclarlo para reconstruir la versión final, no es el caso de este artículo.

Por ello uno de mis objetivos para el nuevo año es la utilización o aprendizaje de Microsoft Parallel Extensions to .NET Framework 3.5, de momento como añadido, pero que estará integrado nativamente en el .NET Framework 4.0, que nos va a permitir trabajar a alto nivel con la programación concurrente y el paralelismo. A partir de entonces utilizaremos el concepto Task en lugar de Thread. Una Task se encargar de dividir el trabajo y lanzar un número óptimo de threads basándose en el número de núcleos que tengamos, evitando el overhead.

También provee de una nueva clase estática, Parallel, que nos permitirá bucles, que no dependan de datos compartidos entre cada iteración, dejando a Parallel Extensions que se encargue de crear las Tasks necesarias para concluir el proceso utilizando todo el potencial de los procesadores o núcleos del sistema.

Aún siendo un ejemplo trivial, se puede ver la mejora de rendimiento en el segundo bucle, siendo exponencial en función del número de iteraciones a realizar.

Sub Main()
    Dim sw As Stopwatch
    sw = Stopwatch.StartNew()

    For iC As Integer = 0 To 30
        Proceso(iC)
    Next
    Console.WriteLine("Tiempo consumido: " & sw.ElapsedMilliseconds.ToString())

    sw = Stopwatch.StartNew()

    Parallel.For(0, 31, Function(iC As Integer) Proceso(iC))

    Console.WriteLine("Tiempo consumido: " & sw.ElapsedMilliseconds.ToString())
    Console.ReadLine()
End Sub
Function Proceso(ByVal iC As Integer) As Boolean
    Console.WriteLine("Indice: " & iC.ToString & " - " & Thread.CurrentThread.ManagedThreadId.ToString())
    Thread.Sleep(ic)
End Function

En este ejemplo se puede observar el resultado, con 30 iteraciones, el tiempo del primer bucle es de 475 y del segundo 287, lo más importante es ver que el primer bucle se genera siempre en el “hilo” 9 y el segundo bucle utiliza varios “hilos” para concluir el proceso más rápidamente.

image

Lo más representativo es ver la gráfica de uso del CPU, se puede observa que el primer bucle (marcado en rojo), no aprovecha todo el procesador, más bien, no aprovecha todos los núcleos disponibles, sin embargo el segundo (en amarillo), mantiene durante todo el bucle el procesador al 100% exprimiendo todos los recursos existentes en ese momento.

graficaparallel

Para este ejemplo he usado un código mucho más “vulgar” para forzar el uso del procesador.

Sub Main()
    Dim sw As Stopwatch
    sw = Stopwatch.StartNew()

    For iC As Integer = 0 To 1000
        Proceso(iC)
    Next
    Console.WriteLine("Tiempo consumido: " & sw.ElapsedMilliseconds.ToString())

    Console.WriteLine("Fin del primer bucle, Pulsa para continuar...")
    Console.ReadLine()

    sw = Stopwatch.StartNew()

    Parallel.For(0, 1001, Function(iC As Integer) Proceso(iC))

    Console.WriteLine("Tiempo consumido: " & sw.ElapsedMilliseconds.ToString())
    Console.ReadLine()
End Sub
Function Proceso(ByVal iC As Integer) As Boolean
    For jC As Integer = 0 To 9999999

    Next
End Function

Todas estas pruebas han sido realizadas sobre un Intel Core2 Duo T8300 a 2,40 Ghz, cuantos más núcleos disponga el ordenador o servidor sobre el que utilicemos esta técnica, más se notará el rendimiento del conjunto.

Más información en:

http://msdn.microsoft.com/en-us/concurrency/default.aspx


Posted mar, feb 3 2009 12:42 by Maverick

Add a Comment

(optional)  
(optional)
(required)  
Remember Me?
MavericK
Powered by Community Server (Non-Commercial Edition), by Telligent Systems