OpenMP

De Wikipedia, la enciclopedia libre
Logo de OpenMP.

OpenMP es una interfaz de programación de aplicaciones (API) para la programación multiproceso de memoria compartida en múltiples plataformas. Permite añadir concurrencia a los programas escritos en C, C++ y Fortran sobre la base del modelo de ejecución fork-join. Está disponible en muchas arquitecturas, incluidas las plataformas de Unix y de Microsoft Windows. Se compone de un conjunto de directivas de compilador, rutinas de biblioteca, y variables de entorno que influyen el comportamiento en tiempo de ejecución.

Por lo anterior, OpenMP puede verse como un modelo de programación paralela para memoria compartida y memoria distribuida en multiprocesadores. Este modelo de programación se basa en tener la disposición de varios hilos de ejecución concurrentes que nos permitan acceder a variables alojadas en zonas de la memoria a las que tienen acceso cada uno de ellos y en directivas de compilación; facilitando la programación al evitar el intercambio explícito de datos entre los diferentes procesadores.[1]

Definido conjuntamente por proveedores de hardware y de software, OpenMP es un modelo de programación portable y escalable que proporciona a los programadores una interfaz simple y flexible para el desarrollo de aplicaciones paralelas, para plataformas que van desde las computadoras de escritorio hasta supercomputadoras. Una aplicación construida con un modelo de programación paralela híbrido se puede ejecutar en un cluster de computadoras utilizando OpenMP y MPI, o a través de las extensiones de OpenMP para los sistemas de memoria distribuida.

Historia[editar]

En la primavera de 1997 Intel forma la OpenMP Architecture Review Board (OpenMP ARB) para el desarrollo de estructuras de repetición en paralelo con el lenguaje Fortran. Para octubre se lanza la versión 1.0 de la especificación OpenMP para Fortran y para el año siguiente empiezan a aparecer las primeras aplicaciones híbridas con MPI y OpenMP.

En 2001 se forma la cOMPunity creada por usuarios de OpenMP, así mismo se crean talleres en América del Norte, Europa y Asia.

En 2002 se comienza la fusión de las especificaciones de Fortran y C/C++ en la versión 2.0.

En 2005 se unifica el uso de C, C++ y Fortran en la versión 2.5, así mismo se realizó el primer taller internacional sobre OpenMP.

En 2008 se incorpora el paralelismo de tareas en la versión 3.0, un problema difícil ya que OpenMP se esfuerza por mantener su naturaleza de subprocesos mientras que se adapta a la naturaleza dinámica de las tareas.

En 2013 se lanza la versión 4.0 la cual admite paralelismo SIMD de dispositivos aceleradores y/o coprocesadores, afinidad de subprocesos y mucho más. Provocando que OpenMP opere más allá de sus límites tradicionales.

En 2015 se lanza la versión 4.5 la cual añade soporte para algunas características de Fortran 2003.

En 2018 se lanza la versión 5.0 la cual ya tuvo dos reediciones que son la 5.1 lanzada en 2020 y la versión más actual la 5.2 lanzada en 2021.

Se ha visto que los principales lanzamientos de OpenMP han aparecido cada 5 años desde la primera versión, con algunas otras versiones menores lanzadas en el medio de este periodo de tiempo. En cualquiera de los dos casos, cuando se publica una versión, siempre hay un período de tiempo antes de que los desarrolladores del compilador se pongan al día para agregar todas las nuevas funciones y correcciones a su lenguaje. Esto se puede notar mucho en las versiones más recientes, las versiones 5.1 y 5.2. Debido a que la compatibilidad con el compilador no está completamente implementada para la versión 5.0 de OpenMP. OpenMP ARB (es decir, la corporación propietaria de OpenMP) ha calificado la versión 5.0 como un "gran avance", ya que en sus propias palabras: "agrega muchas características nuevas que serán útiles para aplicaciones altamente paralelas y complejas", muchas de las cuales fueron solicitadas por los usuarios. Con esto, se espera que una próxima versión 6.0 contenga más soporte para sistemas en tiempo real y algunos cambios en la terminología.[2]

Modelo de ejecución[editar]

OpenMP se basa en el modelo fork-join, paradigma que proviene de los sistemas Unix, donde una tarea muy pesada se divide en K hilos (fork) con menor peso, para luego "recolectar" sus resultados al final y unirlos en un solo resultado (join). El modelo de programación paralela en memoria compartida se basa básicamente en disponer de varios hilos de ejecución concurrentes que pueden acceder a variables alojadas en zonas de la memoria a las que tienen acceso todos ellos. Cada uno de estos hilos tendrá un identificador que le otorgará la posibilidad de tomar decisiones, incluyendo la coordinación de estos para realizar diferentes acciones. Esto facilita mucho la programación, ya que se evita tener que intercambiar explícitamente datos entre los diferentes procesadores.

OpenMP es un sistema de programación de memoria compartida basado en directivas e compilación, es decir, el programador inserta directivas en el código secuencial para indicar al compilador cómo ha de realizar la paralelización, además de incorporar algunas llamadas a funciones específicas. Cuando se incluye una directiva de compilador OpenMP esto implica que se incluye una sincronización obligatoria en todo el bloque. Es decir, el bloque de código se marcará como paralelo y se lanzarán hilos según las características que nos dé la directiva, y al final de ella habrá una barrera para la sincronización de los diferentes hilos (salvo que implícitamente se indique lo contrario con la directiva nowait). Este tipo de ejecución se denomina fork-join.

OpenMP también soporta el modelo de paralelismo de tareas. El equipo de hilos del bloque de código paralelo ejecuta las tareas especificadas dentro de dicho bloque. Las tareas permiten un paralelismo asíncrono. Desde la versión 4.0 lanzada en 2013 admite la especificación de dependencias entre tareas, relegando a la biblioteca de tiempo de ejecución de OpenMP el trabajo de planificar las tareas y ponerlas en ejecución. Los hilos de ejecución irán ejecutando las tareas a medida que éstas estén disponibles (sus dependencias ya estén satisfechas). El uso de tareas da lugar a sincronización con una granularidad más fina. El uso de barreras no es estrictamente necesario, de manera que los hilos pueden continuar ejecutando tareas disponibles sin necesidad de esperar a que todo el equipo de hilos acabe un bloque paralelo. El uso de tareas con dependencias crea un grafo, pudiéndose aplicar propiedades de grafos a la hora de escoger tareas para su ejecución.

Salvo uso de implementaciones hardware de la biblioteca de tiempo de ejecución OpenMP (p.ej. en FPGAs), los sobrecostes de las tareas es mayor. Este sobrecoste ha de ser amortizado mediante el potencial paralelismo adicional que las tareas exponen.

Sintaxis básica[editar]

La sintaxis básica que nos encontramos en una directiva de OpenMP es para C/C++:

# pragma omp <directiva> [cláusula [ , ...] ...]

para Fortran:

!$OMP  PARALLEL <directiva> [cláusulas]
    [ bloque de código ]
!$OMP END <directiva>

A continuación se presentan algunas de las directivas disponibles y cláusulas aplicables a las directivas que ofrece OpenMP.

Directivas[editar]

También se suelen llamar constructores:

  • parallel: Esta directiva nos indica que la parte de código que la comprende puede ser ejecutada por varios hilos. Se crea una tarea implícita para cada hilo perteneciente al equipo de hilos creado por el parallel.

En este fragmento de código se trata de explotar el potencial del paralelismo en una tarea de una serie geométrica que tiende al infinito para la ecuación:

/* Juan Carlos Lanuza Lazo
   MGA-NI 27 / Apr / 2017
   
   Ejemplo de sumatoria paralela de una serie Armonica
*/
#include <omp.h>
#include <stdio.h>

#define LIMIT 1000000000000

int main(void)
{
        double serie = 0;
        long long i;
        #pragma omp parallel
        {
                #pragma omp for reduction(+:serie)
                for(i = 1;i <= LIMIT;i++)
                serie += 1.0/i;
        }

        printf("Valor de la Serie: %.5f\n",serie);
return 0;
}
  • for: El equipo de hilos que se encuentra con el for ejecuta una o más fracciones de iteraciones como resultado de dividir el bucle delimitado por la directiva entre los hilos del equipo (el tamaño de cada partición dependerá de las cláusulas opcionales añadidas al for). Su formato es:
    #pragma omp parallel for [cláusula, ... , cláusula]
    . Cada fracción de iteraciones es una tarea implícita (el programador no ha particionado el bucle manualmente y anotado una tarea por cada trozo).
  • section y sections: Indica secciones que pueden ejecutarse en paralelo pero por un único hilo.
  • single: La parte de código que define esta directiva, solo se puede ejecutar un único hilo de todos los lanzados, y no tiene que ser obligatoriamente el hilo padre.
  • master: La parte de código definida, solo se puede ejecutar por el hilo padre.
  • critical: Solo un hilo puede estar en esta sección. Definen secciones críticas o condiciones de carrera.
  • atomic: Se utiliza cuando la operación atañe a sólo una posición de memoria, y tiene que ser actualizada solo por un hilo simultáneamente. Operaciones tipo x++ o --x son las que usan esta cláusula.
  • flush: Esta directiva resuelve la consistencia, al exportar a todos los hilos un valor modificado de una variable que ha realizado otro hilo en el procesamiento paralelo.
  • barrier: Crea una barrera explícita en la que los hilos se quedan esperando hasta que todo el equipo la haya alcanzado.
  • task: Permite la ejecución asíncrona mediante tareas explícitas. El bloque de código dentro de la directiva task es ejecutado por un único hilo, y la tarea es instanciada por el hilo que se encuentra con la directiva. Por ello es recomendable utilizar esta directiva dentro de una directiva single, evitando crear (y ejecutar) la misma tarea tantas veces como hilos haya. Su formato es: #pragma omp task [cláusulas]. Cuando la tarea se instancia ésta es insertada en una cola de pendientes. Las tareas de la cola de pendientes son seleccionadas por la implementación de OpenMP en función de sus dependencias cumplidas y prioridad. Las tareas seleccionadas pasan a la cola de listas para ejecutar. Los hilos del equipo activo cogen tareas de dicha cola y las ejecutan. Uso de task con single:
#define NUM_ITERS 1000000
#pragma omp parallel
{
    int32_t BS =  NUM_ITERS/omp_get_num_threads();
    #pragma omp single
    {
        for (int i = 0; i < NUM_ITERS; i+= BS) {
            int32_t upper = i+((NUM_ITERS-BS >= 0) ? BS : NUM_ITERS-BS);
            #pragma omp task firstprivate(i,upper)
            {
                for (int ii = i; ii < upper; ii++)
                    a[ii] += b[ii]*c[ii];
            }
        }
        #pragma omp taskwait
    }
}
  • taskyield: Permite a una tarea ceder el control del núcleo del procesador a otra tarea.
  • taskwait: Similar a barrier. El hilo instanciador de tareas se queda esperando en el taskwait hasta que todas las tareas previas al taskwait hayan terminado su ejecución. No sirve para esperar a las tareas anidadas, que requerirán un taskwait anidado. Todos los hilos, sean los que sean, mientras permanecen en una barrera, sea ésta implícita o explícita, comprueban la cola de tareas listas para ejecutar. Si hay tareas pendientes, las ejecutarán.

Cláusulas de visibilidad de datos[editar]

  • shared(valor-i_1, ..., valor-i_N): Los datos de la región paralela son compartidos, lo que significa que son visibles y accesibles por todos los hilos. Por definición, todas las variables que trabajan en la región paralela son compartidas excepto el contador de iteraciones.
  • private(valor-i_1, ..., valor-i_N): Los datos de la región paralela nombrados por private son copiados al área de almacenamiento local del hilo (principalmente su pila), lo que significa que cada hilo los usará como variable temporal. Una variable privada no es inicializada y tampoco se mantiene fuera de la región paralela. Por definición, el contador de iteraciones en OpenMP es privado.
  • firstprivate(valor-i_1, ..., valor-i_N): Cada hilo tiene una copia local del dato. La copia local se inicializa con el valor de la copia global en el momento de encontrarse con la directiva a la que se aplica la cláusula.
  • lastprivate(valor-i_1, ..., valor-i_N): Cada hilo tiene una copia local del dato. La copia global será actualizada por el hilo que ejecuta la última iteración según el orden secuencial de programa.
  • default: Permite al programador que todas las variables de la región paralela sean shared o no para C/C++, o shared, firstprivate, private, o none para Fortran. La opción none fuerza al programador a declarar de que tipo será cada variable en la región paralela.

Las siguientes cláusulas definen operaciones colectivas:

  • reduction(tipo:valor-i_1, ..., valor-i_N): Cada hilo privatiza las variables listadas y al finalizar la sección de la directiva en la que aparece la cláusula, los distintos hilos actualizan la variable global de la que deriva la copia privada realizando la operación indicada por la clásulua reduction. Esto evita condiciones de carrera. El tipo puede ser: +, -, *, /, min, max o definido por el usuario.

Cláusulas de Planificación (Scheduling)[editar]

  • schedule(omp_sched_t tipo, int chunk_size): Esto es útil si la carga procesal es un bucle do o un bucle for. Las iteraciones son asignadas a los threads basándose en el método definido en la cláusula. Los tres tipos de scheduling son:
  1. static: Aquí todas las iteraciones se reparten entre los threads antes de que estos ejecuten el bucle. Se reparten las iteraciones contiguas equitativamente entre todos los threads. Especificando un integer como parámetro de chunk serán repartidas tantas iteraciones contiguas al mismo thread como el chunk indique.
  2. dynamic: Aquí al igual que en static se reparten todas las iteraciones entre los threads. Cuando un thread en concreto acaba las iteraciones asignadas, entonces ejecuta una de las iteraciones que estaban por ejecutar. El parámetro chunk define el número de iteraciones contiguas que cada thread ejecutará a la vez.
  3. guided: Un gran número de iteraciones contiguas son asignadas a un thread. Dicha cantidad de iteraciones decrece exponencialmente con cada nueva asignación hasta un mínimo especificado por el parámetro chunk.

Si no se pone ninguna cláusula schedule, la planificación por defecto es schedule(static,1). Esto es, se reparten las iteraciones del bucle de forma round-robin de una en una.

Claúsulas de dependencias[editar]

  • depends(tipo:valor-i_1,valor-i_2...): Según el tipo de dependencia (entrada, salida o ambos), las tareas serán puestas en la cola de disponibles antes o después de otras tareas, decisión que toma la biblioteca de tiempo de ejecución. Hay tres tipos de dependencias:
  1. in: La dependencia es de lectura. La tarea (task) lee el valor almacenado en la dirección de memoria del valor-i.
  2. out: La dependencia es de escritura. La tarea (task) escribe en la dirección de memoria del valor-i.
  3. inout: La dependencia es de escritura y lectura. La tarea (task) lee y escribe en la dirección de memoria del valor-i.

Si una tarea tiene una dependencia in, y otra tarea instanciada anteriormente tiene una dependencia out sobre el mismo valor-i, entonces la tarea con el in se ejecutará después de la out. Como las lecturas no modifican la memoria, dos tareas con un in sobre el mismo valor-i se pueden ejecutar en cualquier orden y por cualquier hilo disponible. En cambio, dos dependencias de escritura sobre el mismo valor-i serán siempre serializadas, puesto que modifican la memoria. Estos conceptos se asemejan con las dependencias existentes entre instrucciones en un microprocesador con ejecución fuera de orden, en los que se pueden dar tres tipos de dependencias:

  1. RAW: (Read After Write). Lectura después de escritura.
  2. WAR: (Write After Read). Escritura después de lectura.
  3. WAW: (Write After Write). Escritura después de escritura.

La RAW es la única dependencia verdadera e insalvable. Las WAR y WAW se tienen como falsas dependencias puesto que la dependencia existe por tener un mismo nombre (dirección de memoria del valor-i). Renombrando la dirección del valor-i (copiándolo en otra dirección de memoria) se pueden eliminar las falsas dependencias. Si la tarea no lee dicha dirección de memoria, solo la escribe, a efectos prácticos le da igual que otra instrucción anterior la lea. También le da igual que otra instrucción anterior escriba en la misma dirección puesto que no necesita el valor actualizado. Sin embargo, si se utiliza siempre la misma dirección de memoria, hay que preservar el orden de programa para que el último valor sea el que indica el orden de instanciación de las tareas. Así se evita en una ejecución en desorden que una lectura anterior lea un dato que tendría que aparecer más tarde desde el punto de vista de dicha lectura. También se evita el caso en el que la última escritura adelanta a la escritura más vieja, de manera que la vieja será el último valor que quede reflejado cuando el que ha de permanecer debería ser el de la escritura más nueva. Si se renombra la dirección de memoria el orden de las instrucciones no importará, puesto que las tareas dependientes preservarán el orden de ejecución con respecto a la nueva dirección de memoria, eliminando las dependencias WAR y WAW.

Funciones[editar]

La API de OpenMP dispone de una serie de funciones para obtener información y configurar el entorno paralelo. También permite manejar candados (locks) y tomar mediciones de tiempo. Además de las funciones aquí mostradas, cada implementación de OpenMP tiene funciones y variables de entorno propias. Por ejemplo, el OpenMP de GNU (GOMP, GNU OpenMP) dispone de variables de entorno propias.[3]​ Hay partes de la especificación que son dependientes de la implementación por lo que el programa no tiene porqué comportarse igual si el binario se genera primero con una implementación y luego por otra. Por ejemplo, la creación de regiones paralelas anidadas no especifica cómo se han de suministrar los hilos para las regiones anidadas. En el caso de GNU OpenMP mantiene un único equipo de hilos y lo hace crecer o decrecer en función de la cantidad de hilos demandada por la región paralela activa. Esto es una optimización que ayuda a mantener la localidad de datos y disminuir los sobrecostes de creación y destrucción de hilos. Sin embargo, una implementación podría no mantener un equipo único y crear nuevos equipos enteros. Esto provoca la expulsión de los hilos de regiones paralelas predecesoras. La asignación de identificadores a procesadores no sigue siempre el mismo orden salvo que se indique lo contrario. Si el código del programa depende del identificador del hilo para la asignación de datos a procesar, se puede perder la localidad espacial del programa. Crear y destruir múltiples equipos de hilos requiere también de más tiempo que reutilizar equipos de hilos ya existentes.

Las funciones aquí listadas se corresponden con la versión 4.5 del estándar OpenMP.

Funciones para controlar hilos, procesadores y el entorno paralelo[editar]

Estas funciones se enlazan según las normas del lenguaje C y no lanzan excepciones.

  • omp_get_active_level(): Regiones paralelas activas en el momento de invocar la función.
  • omp_get_ancestor_thread_num(int level): Devuelve el TID (Thread ID) en el nivel de anidamiento de paralelismo en cuestión que se corresponde con el hilo que invoca la función.
  • omp_get_cancellation(): ¿Soporta cancelación la implementación de OpenMP en uso? Controlado por la variable de entorno OMP_CANCELLATION.
  • omp_get_default_device(): Obtener el dispositivo por defecto en las regiones target que no tienen cláusula device.
  • omp_get_dynamic(): ¿Está activa la funcionalidad de creación de equipos de tamaño dinámico? Valor controlado por variable de entorno OMP_DYNAMIC o omp_set_dynamic().
  • omp_get_level(): Obtener el nivel de anidamiento en el momento de invocar la función.
  • omp_get_max_active_levels(): ¿Cuál es número máximo de regiones paralelas activas permitido?
  • omp_get_max_task_priority(): ¿Cuál es la prioridad máxima permitida para una tarea explícita?
  • omp_get_max_threads(): ¿Cuál es el número máximo de hilos de la actual región paralela, que no usa num_threads?
  • omp_get_nested(): ¿Se permiten regiones paralelas anidadas? Valor controlado por la variable de entorno OMP_NESTED o función omp_set_nested().
  • omp_get_num_devices(): ¿Cuántos dispositivos aceleradores hay?
  • omp_get_num_procs(): ¿Cuántos procesadores (núcleos) hay disponibles en el dispositivo actual?
  • omp_get_num_teams(): ¿Cuántos equipos de hilos hay en la región paralela actual?
  • omp_get_num_threads(): ¿Cuántos hilos forman el equipo de hilos activo actual? En una sección secuencial devolverá 1. El tamaño por defecto se puede inicializar con la variable OMP_NUM_THREADS. Durante la ejecución se puede usar la cláusula num_threads o la función omp_set_num_threads(int num_threads). Si no se usa nada de lo anterior y el valor de OMP_DYNAMIC está desactivado, se configura un total de hilos correspondiente con el total de procesadores disponibles.
  • omp_get_proc_bind(): ¿Están los hilos anclados a unos procesadores en concreto? La función devolverá el tipo de anclaje (binding), que puede ser uno de los siguientes: omp_proc_bind_false, omp_proc_bind_true, omp_proc_bind_master, omp_proc_bind_close o omp_proc_bind_spread. El tipo de anclaje se establece al inicio de programa con la variable de entorno OMP_PROC_BIND.
  • omp_get_schedule(): ¿Cuál es el método de planificación por defecto?
  • omp_get_team_num(): ¿Cuál es el identificador del equipo de hilos actual?
  • omp_get_team_size(): ¿Cuál es el tamaño del equipo de hilos activo?
  • omp_get_thread_limit(): ¿Cuál es el máximo número de hilos admitido?
  • omp_get_thread_num(): ¿Cuál es el identificador dentro del equipo activo del hilo que invoca la función?
  • omp_in_parallel(): ¿El hilo se encuentra dentro de una región paralela?
  • omp_in_final(): ¿Estamos dentro de una tarea explícita (task) o la región de la tarea explícita se está ejecutando como final?
  • omp_is_initial_device(): En el momento de invocar la función, ¿estamos en el anfitrión o en un acelerador?
  • omp_set_default_device(int device_num): Configura el dispositivo por defecto para las regiones target que no tienen cláusula device.
  • omp_set_dynamic(int dynamic_threads): Activar/desactivar la regulación dinámica del tamaño de los equipos de hilos.
  • omp_set_max_active_levels(int max_levels): Limita el número de regiones paralelas activas.
  • omp_set_nested(int nested): Permitir o no regiones paralelas anidadas.
  • omp_set_num_threads(int num_threads): Configura el número de hilos máximo en un equipo de hilos.
  • omp_set_schedule(omp_sched_t kind, int chunk_size): Configura el método de planificación (schedule) por defecto. El valor de kind puede ser omp_sched_static, omp_sched_dynamic, omp_sched_guided o omp_sched_auto.

Funciones para inicializar, activar, desactivar, comprobar y destruir candados simples y anidados[editar]

  • omp_init_lock(omp_lock_t *lock): Inicializar un candado.
  • omp_set_lock(omp_lock_t *lock): Esperar a que el candado esté inactivo y activarlo para el hilo invocante.
  • omp_test_lock(omp_lock_t *lock): Comprobar si el candado está inactivo y activarlo para el hilo invocante si la comprobación es positiva.
  • omp_unset_lock(omp_lock_t *lock): Desactivar un candado simple.
  • omp_destroy_lock(omp_lock_t *lock): Destruir un candado simple.
  • omp_init_nest_lock(omp_lock_t *lock): Inicializar un candado anidado.
  • omp_set_nest_lock(omp_lock_t *lock): Esperar a que el candado anidado esté inactivo y activarlo para el hilo invocante.
  • omp_test_nest_lock(omp_lock_t *lock): Comprobar si el candado está inactivo y activarlo para el hilo invocante si la comprobación es positiva.
  • omp_unset_nest_lock(omp_lock_t *lock): Desactivar un candado anidado.
  • omp_destroy_nest_lock(omp_lock_t *lock): Destruir un candado anidado.

Funciones para medir el tiempo[editar]

  • omp_get_wtick: Obtener la precisión del cronómetro: cuántos segundos pasan entre dos ticks de reloj.
  • omp_get_wtime: Obtener el tiempo del cronómetro. El tiempo se corresponde con el real, es decir, tiempo de ejecución de programa, no tiempo de proceso. El tiempo de proceso sería la suma de los tiempos de actividad de todos los hilos. La unidad de medida son los segundos. No hay garantía de que dos hilos distintos den el mismo tiempo.

Variables de entorno[editar]

  • OMP_CANCELLATION: Configura la posibilidad de utilizar la directiva #pragma omp cancel
  • OMP_DISPLAY_ENV: Muestra la versión de OpenMP y las variables de entorno.
  • OMP_DEFAULT_DEVICE: Configura el dispositivo utilizado en las regiones target
  • OMP_DYNAMIC: Activa el ajuste dinámico de hilos.
  • OMP_MAX_ACTIVE_LEVELS: Configura la máxima cantidad de regiones paralelas anidadas.
  • OMP_MAX_TASK_PRIORITY: Configura la prioridad máxima de una task.
  • OMP_NESTED: Activa el uso de regiones paralelas anidadas. Si TRUE entonces los miembros de un equipo de hilos pueden crear nuevos equipos de hilos.
  • OMP_NUM_THREADS: Fija el número de hilos simultáneos.
  • OMP_PROC_BIND: Configura si se pueden mover los hilos entre procesadores.
  • OMP_PLACES: Configura las CPUs en las que los hilos deben quedar fijados.
  • OMP_STACKSIZE: Configura el tamaño de la pila de cada hilo.
  • OMP_SCHEDULE: Configura el tipo de schedule por defecto.
  • OMP_THREAD_LIMIT: Configura el máximo número de hilos.
  • OMP_WAIT_POLICY: Configura el algoritmo de espera en las barreras (barrier). Puede ser espera activa o pasiva.

Versiones[editar]

La última versión de OpenMP es la 5.2 . A lo largo de más de 20 años se han publicado las siguientes versiones:[4]

  • OpenMP 1.0: Publicada en octubre de 1997, solo para Fortran. Regiones paralelas, bucles for, barreras y otros mecanismos básicos de sinconización de hilos.
  • OpenMP 1.1: Publicada en noviembre de 1999, una fe de erratas interpretativas.
  • OpenMP 2.0: Publicada en noviembre del 2000 (Fortran) y marzo de 2002 (C/C++). Adiciones como las cláusulas num_threads, y copyprivate o las funciones de obtención de tiempo omp_get_wtime y omp_get_wtick. Permitidos los vectores de longitud variable de C99, pudiéndose usar con ellos las cláusulas que actúan sobre tipos de datos como firstprivate, lastprivate y private.
  • OpenMP 2.5: Publicada en mayo de 2005. Fusiona C, C++ y Fortran. Introduce variables de control internas (ICVs). Redefinición de terminología, clarificación de las explicaciones de la directivas y corrección de erratas.
  • OpenMP 3.0: Publicada en mayo de 2008. Añade tareas explícitas (task). Añadida directiva taskwait. Añadida la planificación auto. Añadida variable de entorno OMP_STACKSIZE y OMP_WAIT_POLICY. Funciones adicionales, cambios en la definición de algunos conceptos.
  • OpenMP 3.1: Publicada en julio e 2011. Adición de cláusulas final, mergeable y taskyield para las tareas explícitas. Directiva atomic extendida con las cláusulas read,write, capture y update. Operadores de reducción min y max añadidos en C y C++. OMP_PROC_BIND añadido.
  • OpenMP 4.0: Publicada en julio de 2013. Añade la especificación de dependencias (depends) en las tareas explícitas así como la posibilidad de especificar regiones de código para ejecutar en dispositivos aceleradores (device offload). Añadida cláusula proc_bind junto con variable de entorno OMP_PLACES y función omp_get_proc_bind. Añadidas directivas para soporte de paralelismo SIMD y aceleradores. Variable de entorno OMP_DEFAULT_DEVICE. Directiva taskgroup. Directiva declare reduction. Directivas cancel y cancellation point junto con la función omp_get_cancellation y variable OMP_CANCELLATION. Variable de entorno OMP_DISPLAY_ENV. Los ejemplos (apéndice A) ahora residen en un documento separado.
  • OpenMP 4.5: Publicada en noviembre de 2015. Añade soporte para algunas características de Fortran 2003. Se añade un parámetro a la cláusula ordered de la directiva de bucles junto con clásusulas nuevas a la directiva ordered para permitir anidar bucles con dependencias entre iteraciones. Con este mismo propósito se añaden las dependencias de tipo source y sink a la cláusula depend. Se añade la cláusula priority para dar pistas a la biblioteca de tiempo de ejecución sobre la prioridad de las tareas. Consecuentemente se añadió la función omp_get_max_task_priority y la variable de entorno OMP_MAX_TASK_PRIORITY. Se añade la cláusula use_device_ptr a la directiva target data y la cláusula is_device_ptr a la directiva target para permitir interactuar con el código nativo de un dispositivo acelerador. Hay más cláusulas relacionadas con dispositivos aceleradores para permitir SIMD, mapear datos desestructurados (no consecutivos en memoria) en aceleradores, funciones de gestión explícita de memoria en aceleradores (reservar memoria, liberar memoria, transferir datos y asociar memoria). Aparte de estas adiciones para aceleradores se mejoran otras directivas y cláusulas ya existentes.
  • OPENMP 5.0: Publicada en noviembre de 2018. OpenMP 5.0 permite la programación de sistemas con aceleradores heterogéneos, como GPUs y FPGAs, mediante la introducción de directivas y cláusulas específicas.Se introdujo una nueva construcción llamada depend para expresar dependencias de datos entre bucles y regiones paralelas.Se agregaron directivas y funciones para el manejo de errores en regiones paralelas, lo que permite un mejor control de la recuperación de errores en programas OpenMP. target y target data: Estas directivas permiten especificar regiones de código que deben ejecutarse en un dispositivo de destino, como una GPU, y gestionar la transferencia de datos entre el host y el dispositivo. teams y distribute: Estas directivas permiten paralelizar bucles anidados en varias unidades de procesamiento, como hilos o equipos de GPU.taskloop: Esta directiva combina la ejecución de tareas con la paralelización de bucles, lo que permite un alto grado de paralelismo y flexibilidad.declare variant: Esta directiva permite definir múltiples versiones de una función o rutina y seleccionar dinámicamente la versión adecuada en tiempo de ejecución.
  • OPENMP 5.1: Publicada en noviembre de 2020. Se introdujeron nuevas cláusulas y directivas para controlar la asignación de tareas en aplicaciones OpenMP, lo que permite una mayor flexibilidad y optimización del rendimiento. OpenMP 5.1 introdujo la capacidad de anidar regiones paralelas, lo que permite una mayor expresividad en la programación paralela. taskloop simd: Esta directiva combina la ejecución de tareas con la paralelización de bucles y el uso de instrucciones SIMD (Single Instruction, Multiple Data) para obtener un rendimiento óptimo en arquitecturas paralelas.depend: Esta cláusula se utiliza en combinación con otras directivas para expresar dependencias de datos entre bucles y regiones paralelas.atomic capture: Esta cláusula permite realizar operaciones atómicas y capturar el valor anterior en una sola instrucción, evitando así la necesidad de múltiples instrucciones atómicas separadas.
  • OPENMP 5.2: Publicada en noviembre de 2021. Se introdujo el soporte para expresiones lambda en OpenMP, lo que permite una programación más concisa y expresiva en el contexto de regiones paralelas. Se agregaron nuevas cláusulas y directivas para permitir un mayor control sobre las dependencias entre tareas en aplicaciones OpenMP. OpenMP 5.2 incluye soporte para el modelo de memoria unificado, lo que facilita la programación de aplicaciones que combinan el uso de memoria compartida y memoria privada. Directivas introducidas en esta versión o mejoradas: loop: Esta directiva proporciona un mayor control sobre la programación de bucles, permitiendo especificar la partición de bucles, el tratamiento de bucles no estructurados y otras características avanzadas.target update: Esta directiva permite controlar explícitamente la transferencia de datos entre el host y el dispositivo de destino en regiones target.requires: Esta directiva se utiliza para especificar los requisitos de características y versiones de OpenMP necesarios para compilar y ejecutar el código.

Compiladores y entornos de programación que soportan OpenMP[editar]

  • GCC de GNU, hasta OpenMP 4.5. Actualmente ya soporta de manera parcial las versiones 5.0 y 5.1 de OpenMP.
  • XL C/C++/Fortran de IBM, hasta OpenMP 3.1 y OpenMP 4.5 parcialmente (aceleración con GPUs NVIDIA).
  • Intel C/C++/Fortran, hasta OpenMP 4.5. Parcialmente soporta las versiones 5.0 y 5.1 de OpenMP
  • Oracle C/C++/Fortran - Oracle Developer Studio 12.6 compilers, hasta OpenMP 4.0.
  • PGI C/C++/Fortran, hasta OpenMP 3.1.
  • Absoft Pro Fortran Fortran, hasta OpenMP 3.1.
  • Lahey/Fujitsu Fortran 95 C/C++/Fortran, hasta OpenMP 3.1.
  • PathScale C/C++/Fortran, hasta OpenMP 2.5, parcialmente OpenMP 3.x y OpenMP 4.0.
  • Cray C/C++/Fortran, hasta OpenMP 4.0 y parcialmente OpenMP 4.5.
  • NAG nagfor, hasta OpenMP 3.1.
  • OpenHU Research Compiler C/C++/Fortran, hasta OpenMP 2.5, parcialmente OpenMP 3.0.
  • LLVM clang, hasta OpenMP 4.0 y OpenMP 4.5 parcialmente (falta el soporte para uso de aceleradores).
  • LLNL Rose Research Compiler C/C++/Fortran, hasta OpenMP 3.0 y algunas características de aceleradores de OpenMP 4.0.
  • Appentra Solutions parallware compiler Parallware Suite C/C++, hasta OpenMP 4.0.
  • Texas Instruments C, hasta OpenMP 3.0 con las directivas de aceleradores de OpenMP 4.0.
  • Mercurium C/C++/Fortran del Barcelona Supercomputing Center, OpenMP 3.1 y OpenMP 4.0, ambos parcialmente.
  • ARM C/C++/Fortran, hasta OpenMP 3.1 y OpenMP 4.0/4.5 parcialmente (faltan las características de uso de aceleradores). Solo para Linux soporta hasta OpenMP 3.5.
  • NVIDIA HPC (y PGI) compiler C/C++/Fortran, soportando hasta la versión 5.1, parcialmente y permitiendo el uso de GPUs.
  • OpenUH Research Compiler C/C++/Fortran: soportando las versiones 2.5 y 3.0 en Linux de 32 o 64 bits.
  • Siemens Sourcery CodeBench Lite – C/C++/Fortran, soportando la versión 4.5 para arquitecturas x64.[5]

Esta lista de compiladores[6]​ muestra que los únicos compiladores y entornos de programación que soportan plenamente el estándar OpenMP son GCC y el Intel Compiler. Las tareas con dependencias (disponibles desde OpenMP 4.0) solo están disponibles en GCC, Intel Compiler, Oracle compiler, PathScale compiler, Cray compiler, clang, Parallware Suite, Mercurium y ARM compiler.

Campos de utilización[editar]

OpenMP resulta de gran utilidad para realizar tareas que requieran procesar un gran volumen de información, ya que permite disminuir los tiempos de carga por medio de la utilización de hilos. Algunos campos que se benefician de esta tecnología son:

  • Súper cómputo.
  • Análisis de procesamiento de imágenes 3D.
  • Procesamiento de datos.
  • Cálculo matemático.

Ventajas de OpenMP[editar]

  • Es sencillo ya que no requiere el envío de mensajes como en MPI.
  • La descomposición de datos es automática.
  • Admite la paralelización incremental del código.
  • Mismo código fuente para las versiones serial y paralela.
  • Permite implementar granularidad gruesa y fina.
  • Eleva la portabilidad.

Desventajas de OpenMP[editar]

  • Requiere un compilador que entienda OpenMP.
  • Solo es eficiente en máquinas con memoria compartida.
  • En general, la eficiencia paralela es baja.
  • La escalabilidad está limitada por el acceso a la memoria.
  • Aumentar la eficiencia requiere reestructurar el código.

Véase también[editar]

Web relacionadas[editar]

Sitio Oficial de OpenMP Archivado el 20 de julio de 2008 en Wayback Machine.

Referencias[editar]

  1. «Cornell Virtual Workshop: How OpenMP Works». cvw.cac.cornell.edu. Consultado el 18 de diciembre de 2022. 
  2. «Cornell Virtual Workshop: History and Evolution». cvw.cac.cornell.edu. Consultado el 18 de diciembre de 2022. 
  3. GNU. «GNU libgomp» (en inglés). Consultado el 18 de agosto de 2017. 
  4. OpenMP Architecture Review Board. «Specifications» (en inglés). Consultado el 16 de agosto de 2017. 
  5. tim.lewis. «OpenMP Compilers & Tools». OpenMP (en inglés británico). Consultado el 2 de junio de 2022. 
  6. OpenMP Architecture Review Board. «OpenMP Compilers» (en inglés). Consultado el 16 de agosto de 2017. 

Friedman, R. (2022, 14 marzo). Reference Guides - OpenMP. OpenMP. https://www.openmp.org/resources/refguides/ Tim.Lewis. (2020, 13 noviembre). OpenMP ARB releases OpenMP 5.1 with vital usability enhancements. OpenMP. https://www.openmp.org/press-release/openmp-arb-releases-openmp-5-1/ OPENMP. (s. f.). | OPENMP API Specification: Version 5.0 November 2018. OPENMP. Recuperado 11 de junio de 2023, de https://www.openmp.org/spec-html/5.0/openmp.html