2008-11-01 46 views
8

He estado escribiendo mucho recientemente sobre programación y programación en paralelo y me doy cuenta de que hay muchos patrones que surgen cuando se trata de computación paralela. Observando que Microsoft ya lanzó una biblioteca junto con la Vista previa técnica de la comunidad de Microsoft Visual C++ 2010 (denominada Parallel Patterns Library), me pregunto cuáles son los patrones comunes de programación en paralelo que ha estado utilizando y encontrando que puede valer la pena recordar. ¿Tienes algún modismo que sigas y patrones que pareces seguir apareciendo mientras escribes programas paralelos con C++?Programación paralela y C++

+0

¿Puede aclarar qué tipo de programación en paralelo le interesa? La programación distribuida usando MPI es considerablemente diferente del paralelismo de nivel de bucle usando OpenMP. – mch

+0

Estoy particularmente interesado en los patrones generales y las expresiones idiomáticas en la programación paralela, ya sea con memoria distribuida o modelos de memoria compartida en una sola máquina o varias máquinas. –

Respuesta

17

Patrones:

  • Produce/Consumer

    • un hilo produce datos
    • un hilo consume los datos paralelismo
  • Loop

    • Si usted puede demostrar que cada bucle es independiente
      cada iteración se puede hacer de un hilo sperate
  • rosca

    • otros hilos hacer de datos de actualización de trabajo y las estructuras redibujar pero una hilo vuelve a dibujar la pantalla.
  • evento principal de rosca

    • múltiples hilos pueden generar eventos
    • Un hilo tiene que procesa los eventos (como orden es importante)
    • debe tratar de separar el hilo de eventos/Re -Draw Thread
      Esto (ayuda) previene que la IU se congele
      Pero puede causar un exceso de sorteos si no se hace con cuidado.
  • Grupo de Trabajo

    • Un conjunto de hilos espera a puestos de trabajo en una cola.
    • El hilo extrae un elemento de trabajo de la cola (esperando si no hay ninguno disponible).
      El hilo funciona en un elemento de trabajo hasta completar
      Una vez que el hilo finalizado vuelve a la cola.
+0

¡Gran lista, gracias! –

+0

¿Qué sucede si el hilo de volver a dibujar necesita usar las estructuras de datos al dibujar?Quiero decir, ¿no es peligroso que lea de ellos mientras los otros hilos los están actualizando? – leod

+0

A menos que el hilo de volver a dibujar esté asociado con un objeto activo, donde los cambios se "serializan" y el objeto activo "posee" las estructuras de datos modificadas. –

2

Primero tiene que elegir entre la informática de memoria compartida y la informática de uso compartido. La memoria compartida es más fácil, pero no escala muy bien - que va a utilizar compartido nada si bien

a) disponer de un clúster, en lugar de un sistema multiprocesador, o

b) si tiene muchas CPU (por ejemplo,> 60) y un alto grado de memoria no uniforme

Para la memoria compartida, la solución común es usar hilos; son fáciles de entender como concepto y fáciles de usar en la API (pero difíciles de depurar).

Para compartir nada, usa algún tipo de mensaje. En informática de alto rendimiento, MPI se establece como el middleware de mensajería.

También necesita diseñar una arquitectura para las actividades paralelas. El enfoque más común (nuevamente porque es fácil de entender) es el patrón agricultor-trabajador (a.k.a. maestro-esclavo).

+0

Para ser justos, no necesariamente tiene que elegir solo uno; puede crear una arquitectura que admita ambos. Pero estos puntos son válidos; es necesario que tenga claro a qué soporte, ya que los requisitos (y, a menudo, los diseños) son bastante diferentes. –

2

Patrones ejecución paralela

programación paralela estructurado con patrones deterministas es un enfoque de alto nivel basado principalmente en una colección de patrones de ejecución en paralelo recurrentes, a menudo se hace referencia a los esqueletos como algorítmicos o construcciones paralelas , que resumen la descripción del programa y oculta los detalles de subprocesos múltiples de bajo nivel y muchas complejidades inherentes al paralelismo de los programadores.

Estos patrones reutilizables automatizan muchas rutinas paralelas relacionadas con el paradigma, como sincronización, comunicación, partición de datos o programación de tareas y las manejan internamente. Este enfoque de alto nivel intenta el modelo tradicional de bajo nivel de bloqueo de hilos con más abstracción y una forma más fácil de expresar el paralelismo y enfocar la productividad y la programabilidad en lugar del rendimiento.

Hay muchos patrones de uso común, tales como: Map-Reduce, Tenedor-Ingreso, Pipeline o bucle paralelo ...

Papeles

"Programación Paralela estructurado con patrones deterministas" es un documento en el que discutir estos patrones. También puede ver "MHPM: Modelo de Programación Híbrido Multi-Escala: Una Metodología de Parallelización Flexible" que describe una implementación en C++ de este enfoque llamado XPU.

Biblioteca

XPU es una biblioteca basada en tareas C++ compuesta de un conjunto de patrones de ejecución reutilizables. Permite la expresión de varios tipos de paralelismo en varios niveles de granularidad dentro de un único modelo de programación homogéneo. Es fácil de usar e ilustra el interés en el uso de patrones para diseñar programas paralelos.

Por ejemplo que permite la expresión de:

  1. Patrón de tareas Paralelismo:

    patrón simple o jerárquica Tenedor/Join ejecución con algunas características tales de detección y protección de los datos compartidos como automático.

  2. patrón de datos Paralelismo:

    patrón bucle paralelo con la partición de datos escalable.

  3. patrón temporal Paralelismo:

    Pipeline tendencia de ejecución.

+0

¿Podría incluir una muestra de algún código que haya escrito utilizando la biblioteca XPU? –

0

Tiene lo básico que se atornilla al paralelismo con partes de un programa. C++ 17 está obteniendo muchos de ellos (por ejemplo, versiones paralelas de foreach, sort, find y friends, map_reduce, map, reduce, prefix_sum ...) ver C++ Extensions for Parallelism

Luego tienes los elementos como continuaciones. Piense en std::future pero con continúa. Hay pocas maneras de implementar esto (boost tiene una buena versión ahora, ya que la estándar no tiene un próximo (...) o luego (...) método, pero la gran ventaja es que uno no tiene que esperar que haga la siguiente tarea

auto fut = async([](){..some work...}).then([](result_of_prev){...more work}).then... ; 
fut.wait(); 

la falta de sincronización entre las tareas posteriores es importante ya que la comunicación entre tareas/hilos/... es lo que ralentiza programas paralelos hacia abajo.

Así que con eso el paralelismo basado en tareas es realmente agradable. Con un planificador de tareas, simplemente pasa las tareas y se aleja. Pueden tener métodos, como un semáforo, para comunicarse, pero eso no es obligatorio. Ambos Intel Thread Building Blocks y Microsoft Parallel Pattern Library tienen fa Cilitudes para esto.

Después de eso tenemos el patrón de tenedor/unión. No implica crear N hilos para cada tarea. Solo que tienes estas N, cosas idealmente independientes, para hacer (fork) y cuando terminan tienen un punto de sincronización en algún lugar (join).

auto semaphore = make_semaphore(num_tasks); 
add_task([&semaphore]() {...task1...; semaphore.notify(); }); 
add_task([&semaphore]() {...task2...; semaphore.notify(); }); 
... 
add_task([&semaphore]() {...taskN...; semaphore.notify(); }); 
semaphore.wait(); 

Desde arriba puede comenzar a ver el patrón que se trata de un diagrama de flujo. El futuro es (A >> B >> C >> D) y Fork Join es (A | B | C | D). Con eso puedes combinarlos para formar un gráfico. (A1 >> A2 | B1 >> B2 >> B3 | C1 | D1 >> D2 >> (E1 >> E2 | F1)) Donde A1 >> A2 significa que A1 debe preceder a A2 y A | B significa que A y B puede correr concurrentemente Las partes lentas se encuentran al final de los gráficos/subgrafos donde las cosas se juntan.

El objetivo es encontrar partes independientes del sistema que no necesiten comunicarse. Los algoritmos paralelos, como se señaló anteriormente, son en casi todos los casos más lentos que sus equivalentes secuenciales hasta que la carga de trabajo sea lo suficientemente alta o el tamaño sea lo suficientemente grande (suponiendo que la comunicación no sea demasiado hablador). Por ejemplo, ordenar. En una computadora de 4 núcleos obtendrás alrededor de 2.5X el rendimiento dado o recibido porque la fusión es hablante y requiere mucha sincronización y no funciona todos los núcleos después de la primera ronda de fusión. En una GPU con una N que es muy grande, se puede usar una clase menos eficiente, como Bitonic, y termina siendo muy rápida porque tienes muchos trabajadores para potenciar el trabajo y todos están tranquilos haciendo sus cosas.

Algunos trucos para reducir la comunicación incluyen, usar una matriz de resultados para que cada tarea no intente bloquear un objeto para insertar un valor. A menudo, la reducción de estos resultados puede ser muy rápida.

Pero con todos los tipos de paralelismo la lentitud proviene de la comunicación. Reducirlo.

Cuestiones relacionadas