hdolder.com srl  

       Software R&D
     hdc Home    |    Contenido    |    KO1    |    Director    |    Direcciones    |    email    |   Twitter   
  M&P TBW - Implicit Parallelism
                                           v191

Procesadores multi-core

El procesamiento paralelo está ocupando el centro del escenario con la aparición de los procesadores multi-core de bajo precio.

La conocida Ley de Moore (formulada por Gordon E. Moore, cofundador de Intel, en 1965) se aplica ahora a los procesadores multi-core y se espera que el numero de cores se duplique cada 18 meses.

Hace dos años (2009) una PC de alta gama era "dual-core", actualmente es "quad-core",  para el año 2015 se esperan 16-cores y para el año 2020 64-cores.

Pero las aplicaciones puramente "secuenciales" actuales no permiten aprovechar los procesadores paralelos y el aprovechamiento queda limitado básicamente al multi-tasking que el Sistema Operativo realiza.

Para el aprovechamiento de la gran cantidad de procesadores paralelos prevista se requiere introducir cambios importantes en las arquitecturas de software y en los procesos de desarrollo actuales, lo que implica un importante cambio cultural.

Para facilitar las cosas se trata de implementar sistemas de desarrollo que provean "paralelización implícita".

Implicit Parallelism

Implicit Parallelism (IP) es un concepto desarrollado durante la década de los 90s [R1] y es uno de los Top Concepts sobre los que se basa M&P.

La idea es que el desarrollador se pueda desentender de los detalles del procesamiento paralelo para concentrarse en los detalles específicos de la aplicación. Los detalles del procesamiento paralelo son manejados automáticamente por el compilador, la arquitectura (framework) y/o el Runtime.

El IP es el Santo Grial del procesamiento paralelo porque permitiría el desarrollo de sistemas por personas no especializadas en procesamiento paralelo al esconder (abstraer) los mecanismos de threading, buffering, locking y synchronization indispensables para el procesamiento paralelo.

El IP pretende que el nivel de complejidad de las aplicaciones paralelas sea comparable al nivel de complejidad de las aplicaciones secuenciales.

Los mecanismos de threading, buffering,  locking y synchronization son intrínsecos del procesamiento paralelo y en algunas aplicaciones puede ser necesario que el desarrollador los maneje en forma directa (Explicit Parallelism). En la practica el esquema ideal es aquel que permite tanto la paralelización implícita como la paralelización explicita [R2].

En la búsqueda de ese Santo Grial se ha encontrado que los lenguajes de programación declarativa [R1] y el procesamiento Dataflow [R3][R4] permiten una aproximación natural a la paralelización implícita.

En M&P se integran la programación declarativa (en lenguaje XAML) y el procesamiento Dataflow.

En M&P se extiende el runtime de XAML mediante la introducción de "Queued Properties" de manera de lograr un "Parallel XAML".

Los BLOCKS de M&P, que son UserControls XAML con algunas interfaces adicionales requeridas por el MPR (M&P Runtime), tienen capacidad de procesamiento paralelo y operan en paralelo por default.

Los elementos que conectan los BLOCKS en una aplicación son BLOCKS M&P especialmente diseñados para implementar el concepto de paralelización implícita.

Si bien la operación de M&P es paralela por default, el grado de paralelismo puede ser limitado fácilmente por el desarrollador.

La posibilidad de limitar el paralelismo es importante porque el paralelismo tiene un costo elevado en términos del Runtime y del Sistema Operativo. Un paralelismo de "grano fino" puede ser contraproducente ya que rige la ley de rendimientos decrecientes.

En general en el paralelismo a nivel de BLOCKS implementado en M&P los beneficios superan a los costos y aumentan al aumentar la cantidad de procesadores paralelos disponibles.

Paralelismo a nivel de métodos

Como vimos antes M&P introduce el concepto de paralelismo a nivel de BLOCKS (nivel macro) además del uso de métodos asincrónicos (nivel micro).

Buscando lograr el mayor grado de paralelismo implícito en el uso de métodos asincrónicos, en C# 5.0 se introducen dos nuevas keywords: await y async. Estas keywords permiten desterrar definitivamente (del código fuente) los callbacks y permiten, como veremos en un ejemplo muy simple, que el código de la programación asincrónica sea similar (isomorfo) al de la programación secuencial.

Además de los keywords await y async el esquema tiene un elemento central: el concepto de Task, proveniente de la Task Parallel Library (TPL) [R4] [R9] introducida en .NET 4.

Un Task [R6] representa una operación asincrónica. Cada operación asincrónica tiene asociado un objeto Task<TResult>.

TResult es el tipo de dato retornado por la operación asincrónica encapsulada en el objeto Task. Si la operación asincrónica no retorna un valor (el método es "void") se utiliza Task<>.

Un objeto Task no solo representa una operación asincrónica sino que también permite manipularla (por ejemplo, cancelarla). Pero además, como veremos, un objeto Task es un objeto "awaitable".

Para facilitar la comprensión de los conceptos anteriores y de su interoperación, consideremos un ejemplo super simplificado en el que logramos que dos métodos: A y B operan en paralelo.

En este ejemplo ambos métodos reciben y devuelven números enteros.

Sin paralelismo el código secuencial seria:

        public void Test()
        {
            int y = A(1111);
            int x = B(2222);
            int z = x + y;
        }

        int A(int val)
        {
            return val + 111;
        }

        int B(int val)
        {
            return val + 333;
        }

La correspondiente versión paralelizada (que analizaremos mas abajo) es:

        public async void TestAsync()
        {
            var task = AAsync(1111);
            task.Start();
            int x = B(2222);
            int y = await task;
            int z = x + y;
        }

        public Task<int> AAsync(int val)
        {
            return new Task<int>(() =>
                {
                    return val + 111;
                });
        }

        int B(int val)
        {
            return val + 333;
        }

Lo primero que notamos es que no hay callbacks y que para el método A hemos creado una versión asincrónica AAsync que retorna inmediatamente un objeto Task<int>.

Vemos que en el constructor del Task<int> hemos incluido un delegate que encapsula el código del método A original.

En el encabezamiento de TestAsync hemos incluido el keyword "async" para indicarle al compilador que este método contiene "awaits" y que deberá generar internamente el sofisticado código relacionado con la operación de Tasks (el codigo que genera internamente es Continuation Passing Style y para cada Task el compilador crea un "state machine" ).

En TestAsync la sentencia

            task.Start();

arranca la operación del método AAsnc.  Inmediatamente después se invoca el método sincrónico B. A partir de ese momento ambos métodos están operando en paralelo.

Antes de poder realizar la operación final

            int z = x + y;

una vez que el método sincrónico B haya terminado debemos esperar ("await") que el método AAsync haya terminado y obtener su resultado, lo que se logra con la sentencia

            int y = await task;

Si previamente al "await" el método AAsync ya había terminado el await retorna el valor resultado inmediatamente.

Vemos que el paralelismo implícito logrado es muy bueno ya que el código asincrónico es isomorfo con el código secuencial al haberse eliminado los callbacks.

Este modelo de programación asincrónica es el utilizado en el nuevo Framework WinRT de Windows 8 para crear aplicaciones Metro y es el standard de MS para el futuro.

El ejemplo super simplificado anterior muestra la esencia del esquema desde el punto de vista del paralelismo implícito, pero la Task Parallel Library (TPL) es muy amplia y sofisticada ya que cubre todos los aspectos y escenarios relacionados con el procesamiento paralelo.

La TPL cubre también la operación Dataflow (TDF) [R5] que a nivel micro complementa al M&P Dataflow. Ver "Parallel XAML".

 FAQs sobre la TPL

Se pueden crear Tasks dentro de un Task ?

Si, TPL permite crear child y nested Tasks dentro de Tasks [R9].

Cada Task opera en un thread diferente ?

Si, por default los threads utilizados provienen del ThreadPool, que ha sido potenciado en .NET 4 [R7].

TPL permite también ejecutar Tasks en la UI Thread y en threads independientes (fuera del ThreadPool) en el caso de Tasks de larga duración  (long-lived Tasks).

En versiones anteriores de .NET el ThreadPool utilizaba, para encolar sus requerimientos de threads, una única cola FIFO (first-in, first-out) en cada Application Domain. Esta única "cola global" compartida constituía el cuello de botella del ThreadPool.

En .NET Framework 4 el ThreadPool utiliza un conjunto de colas que permite reducir los tiempos de encolado y desencolado de requerimientos.

En el conjunto existe una "global queue" y hay una "local queue" por cada thread del pool [R10].

Las "top level tasks" (que no son creadas dentro de otro Task) se encolan en el "global queue".

Los child y nested tasks se encolan en el "local queue" del thread en el cual esta siendo ejecutada su "parent task" .

Se utiliza un algoritmo de "work stealing" (si una cola queda vacia toma requerimientos de otras colas).

Cual es la funcion de los Task Schedulers ?

Los Task Scheduler [R7] encolan los Task en Threads y luego ejecutan y coordinan los Tasks.

El "default scheduler" de TPL utiliza threads del ThreadPool.

El ThreadPool permite optimizar el procesamiento de Task de corta duración (short-lived Tasks).

TPL balancea automáticamente la carga sobre todas las CPU (cores) disponibles ?

Si, TPL se asegura que no haya CPUs inactivas mientras haya Tasks pendientes.

Referencias

Ver también

Links sugeridos

 

  TBW The BLOCKS World

©2012 hdolder.com srl  

CaPxALEA4
2012-01-07