OpenMP (Open Multi-Processing) és una interfície de programació d'aplicacions (API) que suporta programació multiprocés amb memòria compartida multi-plataforma en C/C++ i Fortran a moltes arquitectures, incloent les plataformes Unix i Microsoft Windows. Consisteix en un conjunt de directives de compilador, rutines de biblioteques, i variables d'entorn que afecten al comportament en temps d'execució.

OpenMP
Modifica el valor a Wikidata

Tipusestàndard tècnic i programari Modifica el valor a Wikidata
Versió estable
5.2 (9 novembre 2021) Modifica el valor a Wikidata
Característiques tècniques
Sistema operatiumultiplataforma Modifica el valor a Wikidata
Escrit enC++ i C Modifica el valor a Wikidata
Més informació
Lloc webopenmp.org Modifica el valor a Wikidata
Stack ExchangeEtiqueta Modifica el valor a Wikidata

Definit conjuntament per un grup dels principals fabricants de maquinari i programari, OpenMP és un model portable i escalable que dona als programadors una interfície simple i flexible per a desenvolupar aplicacions paral·leles per a plataformes que van des de l'escriptori fins als supercomputadors.

Una aplicació construïda amb el model híbrid de programació paral·lela pot executar-se en un raïm d'ordinadors fent servir OpenMP i Message Passing Interface (MPI).

Introducció

modifica

OpenMP és una implementació de multithreading, un mètode de paral·lelització pel qual el "thread" (fil, en anglès) mestre (una sèrie d'instruccions executades consecutivament) es "bifurca" en un nombre determinat de "threads" esclaus i la tasca es divideix entre ells. Els threads llavors s'executen concurrentment, amb l'entorn d'execució ubicant els threads a diferents processadors.

La secció de codi que es vol que s'executi en paral·lel és marcada corresponentment, amb una directiva de preprocessador que farà que els threads es formin abans que la secció sigui executada. Cada thread té un "id" (identificador) associat que pot ser obtingut fent servir una funció (anomenada omp_get_thread_num() a C/C++ i OMP_GET_THREAD_NUM() a Fortran). L'id de thread és un enter, i el thread mestre té una id de "0". Després de l'execució del codi paral·lelitzat, els threads s'"uneixen" de nou al thread mestre, que continua endavant fins al final del programa.

Per defecte, cada thread executa la secció de codi paral·lelitzada independentment. Es poden fer servir "construccions de compartició de treball" per a dividir una tasca entre els threads de forma que cada thread executi la part de codi que té assignada. Fent servir OpenMP d'aquesta forma es pot aconseguir tant el paral·lelisme de tasques com el paral·lelisme de dades.

L'entorn d'execució assigna els threads a processadors depenent de l'ús, la càrrega de la màquina i altres factors. El nombre de threads pot ser assignat per l'entorn d'execució basant-se en variables d'entorn o en codi fent servir funcions. Les funcions OpenMP estan incloses en un fitxer de capçalera amb el nom "omp.h" a C/C++.

Història

modifica

A l'Octubre de 1997, L'OpenMP Architecture Review Board (ARB) va publicar el seu primer estàndard, OpenMP per a Fortran 1.0.[1] A l'Octubre de l'any vinent van publicar l'estàndard per C/C++.[2] A l'any 2000 va sortir la versió 2.0 de l'estàndard Fortran,[3] i dos anys més tard la mateixa versió per C/C++.[4] La versió 2.5 és una combinació entre les especificacions de C/C++/Fortan, que va arribar al 2005.[5]

Fins a la versió 2.0, OpenMP es va centrar principalment en formes de paral·lelitzar bucles molt regulars, que es produeixen per exemple en la programació numèrica orientada a matrius, on es coneix el nombre d'iteracions del bucle al iniciar l'operació. Això es va reconèixer com una limitació del paral·lelisme i a partir d'aquest punt es van afegir diverses extensions de tasques paral·leles a les implementacions. El 2005, es va intentar estandarditzar el paral·lelisme de tasques, publicant una proposta al 2007, inspirada en les característiques del paral·lelisme de tasques de Cilk, X10 i Chapel.

Així doncs, la versió 3.0 es va llançar al Maig de 2008,[6] afegint noves directives per donar suport al paral·lelisme de tasques i la seva construcció, ampliant significativament l'abast d'OpenMP més enllà de les construccions de bucles paral·lels que formaven la majoria d'OpenMP 2.0.

Al Juliol de 2013 surt la versió 4.0.[7] Aquesta afegeix o millora diversos aspectes de la versió anterior. Afegeix suport per acceleradors, mecanismes per descriure regions de codi on còmput o memòria s'ha de moure cap algun altre dispositiu. Inclou suport per vectorització SIMD. Gestió d'errors (Error Handling) per millorar la resistència i estabilitat de les aplicacions en relació amb errors a nivell de sistema, execució i definits per l'usuari. Thread affinity, mecanismes per definir on executar els threads OpenMP. Extensions pel paral·lelisme de tasques, com el seu agrupament o avortament i major suport a la seva sincronització i dependència de dades. Suport per Fortran 2003. Reduccions definides per l'usuari i Operacions atòmiques.

Al Novembre de 2015, s'anuncia la versió 4.5.[8] Aquesta versió ofereix una millora del suport per a programació de dispositius acceleradors i GPU, la paral·lelització de bucles amb dependències ben estructurades i el suport per a bucles doacross, entre altres especificacions.

Al Novembre de 2018 surt la última versió d'OpenMP fins al moment 5.0.[9] Aquesta versió inclou finalment un suport complet per dispositius acceleradors, que encara s'estava desenvolupant a la versió 4.5. Millora de la depuració i l'anàlisi del rendiment. Suport per les últimes versions Fortran 2008, C11 i C++17. Suport per construccions de bucles “fully descriptive”, que dona al compilador més llibertat per triar una millor implementació. Sistemes de memòria multinivell, mecanismes d'assignació de memòria que col·loquen les dades en diferents tipus d'aquestes. I millora de la portabilitat. Aquesta versió és l'actual de les especificacions de l'API.

Interfície d'usuari

modifica

Directives de compilació

modifica

A OpenMP es troben una sèrie de construccions de control i d'atributs de dades, que s'estenen al llenguatge base (F77, f90, C i C++). Els compiladors, per defecte, ometen aquestes directives OpenMP si no s'activen mitjançant un flag "-mp" o "-fopenmp".

Biblioteca i variables d'entorn

modifica

OpenMP disposa d'un conjunt de funcions per controlar paràmetres incloses a la llibreria de C "omp.h". Un exemple seria la definició del número de threads mitjançant call omp_set_num_threads(64). Per una altra banda, disposa de variables d'entorn que permeten controlar paràmetres de forma diferent, com per exemple setenv OMP_NUM_THREADS(8).

Elements principals

modifica

Els elements principals d'OpenMP són directives per a la creació de threads, distribució de treball, gestió de dades en l'entorn, sincronització de threads, rutines d'execució a nivell d'usuari i variables d'entorn. Aquestes directives a C/C++ s'anomenen #pragmas.

Directives vectorials

modifica

En un primer nivell, podem diferenciar les directives SIMD o vectorial, destinades a paral·lelisme a nivell de dades. La directiva #pragma omp simd, indica al compilador que el bucle següent es vectoritzable.

Segons la versió del compilador apuesta directiva pot ser ignorada, que el processador trobi errades en dependències de dades que impedeixin la paral·lelització d'aquest o bé que utilitzi aquesta directiva com a pista per a utilitzar instruccions vectorials. La directiva #pragma omp declare simd indica al compilador que ha de generar una versió SIMD d'una funció que podrà ser cridada des d'un bucle vectoritzat. Per tant, la funció declarada amb aquesta directiva podrà també ser executada sense necessitat d'aplicar paral·lelisme de dades.

Tot i que no ho indiquem explícitament, el compilador analitza el codi i tracta de vectoritzar els bucles de forma automàtica (sempre que s'indiqui al compilador un nivell d'optimització adequat mitjançant clags com -O3 o Ofast). Per comprobar que realment el compilador ha vectoritzat un bucle, podem analitzar el codi màquina on descobrirem una "p" als mnemònics de les instruccions assembler, fet que confirma la utilització de paralel·lisme SIMD.

Creació de threads

modifica

La directiva #pragma omp parallel, basada en un model d'execució FORK-JOIN que indica al compilador que el codi serà executat per diferents threads, creant així una regió paral·lela. El thread encarregat de crear la resta s'anomena master amb id 0. Un cop realitzat el fork, el compilador reparteix trossos d'iteracions del següent bucle als diferentes threads. Per defecte la variable d'iteració del bucle és privada per a cada thread però cal tenir en compte que si no s'especifica el contrari, la resta de variables del codi sera compartides per tots els threads. També és destacable que totes les variables creades a l'interior d'aquesta construcció seran creades per a cada thread.

Directives de compartició de treball

modifica

Les directives de compartició de treball són utilitzades per a especificar com es reparteix el treball a un o tots els threads.

  • for: Utilitzat per repartir iteracions de bucles entre diferents threads.
  • section: Utilitzat per distribuir un bloc de codi a un únic thread.
  • single: Especifica que un tros de codi serà executat per un únic thread, amb un barrier implícit al final.
  • master: Especifica que un tros de codi serà executat per un únic thread, que serà el master. No conté un barrier al final.

Clàusules

modifica

Clàusules de compartició de dades

modifica
  • private(list): Especifica que cada subprocés ha de tenir la seva propia instància d'una variable. Una variable privada no és inicialitzada i tampoc es manté el seu valor fora de la regió paral·lela. Per definició, el comptador d'iteracions en OpenMP és privat.
  • firstprivate(list): Realitza la mateixa funció que la clàusula private, però inicialitza la variable privada amb el valor de la variable del thread master.
  • lastprivate(list): Les variables de la lista s'inicialitzen a 0 i la variable del thread master és actualitzada amb el valor de l'última iteració a realitzar.
  • shared(list): Especifica que les variables incloses a la llista són compartides per tots els threads.
  • default(shared | none): Es pot definir com a shared que significa que qualsevol variable de la regió paral·lela es tractarà com si estigués a l'interior d'una clàusula shared o no, on la variable serà tractada com a privada o compartida. Per defecte, el valor de la clàusula default serà shared.
  • reduction(reduction-identifier: list): Especifica que les variables de la llista estan subjectes a un operador de reducció com sum, min/max o count. La reducció es pot implementar amb una crida a funció.

Clàusules de sincronització

modifica
  • critical: El bloc de codi s'executa per un únic thread en cada moment, fet que evita condicions de carrera.
  • atomic: Les actualitzacions en memoria de la següents instrucció s'executen atòmicament, és a dir, microinstrucció a microinstrucció. El compilador utilitza instruccions hardware que proporcionen un millor rendiment que utilitzant critical
  • ordered: El block s'executa en l'ordre en que les iteracions s'executarien en un bucle seqüencial.
  • barrier: Cada thread espera que la resta acabi la seva execució. Les directives de compartició de treball contenen un barrier implícit.
  • nowait: Especifica que els threads que acabin el treball assignat no han d'esperar a que la resta finalitzin la seva execució. En absència d'aquesta clàusula, els threads esperaran a l'execució de la resta.

Clàusules de planificació

modifica
  • schedule(type, chunk): Utilitzat en directives de compartició de treball per definir el mètode utilitzat per repartir iteracions d'un bucle entre diferents threads.
    • static: Les iteracions del bucle són assignades a cada thread abans de l'execució. Per defecte, les iteracions es reparteixen de forma equitativa entre tots els threads, però mitjançant el paràmetre chunk especifiquem el nombre chunk d'iteracions que assignarem a un thread.
    • dynamic: S'assignen una sèrie d'iteracions als threads, que quan acaben l'execució, obté noves iteracions per a realitzar. El paràmetre chunk especifica quantes iteracions s'assignen a cada thread cada vegada.
    • guided: S'assigna una gran quantitat d'iteracions a cada thread, que va disminuint a mesura que avança l'execució. D'aquesta manera, cada thread rep menys treball cada vegada que en demana més. El paràmetre chunk indica quin és el nombre mínim d'iteracions que pot arribar a rebre cada thread.

Control amb if

modifica
  • if([ parallel :] scalar-logical-expression): Permet establir una expressió tenera, que segons si s'evalua com a 1 o un 0, executarà el codi de la regió paral·lela en paral·lel o de forma seqüencial, respectivament.

Còpia de dades

modifica
  • copyin(list): Realitza la mateixa funció que la clàusula private, però inicialitza la variable privada amb el valor de la variable del thread master. És útil en variables definides mitjançant threadprivate(list) que permet definir variables privades associades a cada thread d'una regió paral·lela que mantindran el mateix valor cada cop que el thread associat sigui creat.
  • copyprivate: Utilitzat amb single per copiar valors d'objectes privats d'un thread a objectes d'altres threads.

Distribució física de threads

modifica
  • proc_bind(master | close | spread): Controla el thread affinity dels threads. La opció master obliga a tots els threads del team a ser assignats al mateix lloc del thread master. Amb l'opció close, els threads són assignats a llocs propers al del thread master. En cas que siguie spread, els threads s'assignen de forma dispersa.

Definició del número de threads

modifica
  • num_treads(scalar-integer-expression): Permet definir el nombre de threads que executaran el codi de la regió paral·lela.

Variables d'entorn

modifica

Les variables d'entorn són un mètode per alterar les característiques d'execució de les aplicacions que utilitzen OpenMP, com per exemple el número de threads que es crearan per defecte a les regions paral·leles mitjançant OMP_NUM_THREADS.

Implementacions[10]

modifica

Compiladors amb implementació d'OpenMP 3.0:

  • GCC 4.3.1
  • Compilador Mercurium
  • Compiladors Intel Fortran i C/C++, versions 11.0 i 11.1 i Intel C/C++ and Fortran Composer XE 2011 and Intel Parallel Studio.
  • Compilador IBM XL C/C++
  • L'actualització 1 de Sun Studio 12 té una implementació completa d'OpenMP 3.0

Nombrosos compiladors soporten OpenMP 3.1:

Diferents compiladors soporten OpenMP 4.0:

  • GCC 4.9.0 per C/C++, GCC 4.9.1 per Fortran
  • Compiladors Intel Fortran i C/C++ 15.0
  • LLVM/Clang 3.7 (partial)

Compiladors auto-paral·lelitzadors que generen codi font amb directives d'OpenMP:

  • iPat/OMP
  • Parallware
  • PLUTO
  • ROSE(Compiler framework)
  • S2P by KPIT Cummins Infosystems Ltd.

Diversos profilers i debuggers també soporten OpenMP:

  • Allinea Distributed Debugging Tool (DDT)
  • Allinea MAP
  • ompP
  • VAMPIR

Avantatges i inconvenients

modifica

Avantatges

modifica
  • Codi multithreading portable.
  • Simple, ja que no necessita pas de missatges com a MPI.
  • La descomposició de dades es gestionada per directives automàticament.
  • Escalabilitat, si ho comparem amb MPI, en sistemes de memòria compartida.
  • Pot treballar només en una part del programa, i no necessita que el codi original sigui modificat pel que es redueix la probabilitat d'introduir bugs.
  • Codi unificat per aplicacions sèrie i paral·leles. Les directives d'OpenMP són tractades com a comentaris si no s'especifica el flag corresponent.
  • Permet paral·lelisme amb granularitat fina o gruixuda, depenent de les necessitats i l'entorn de cada aplicació.
  • Pot ser utilitzat en acceleradors com GPGPU.

Inconvenients

modifica
  • Risc d'introduir bugs en debugging i condicions de carrera.
  • La seva execució només es eficient en multiprocessadors amb memòria compartida.
  • Necessita un compilador que suporti OpenMP (gcc i icc en serien bons exemples).
  • L'escalabilitat es superior a la de MPI però ve determinada per l'arquitectura de memoria.
  • No té suport per a compare-and-swap.
  • No té un bon tractament d'errors.
  • És possible reduir el rendiment fàcilment amb un ús compartit fals (false sharing).

Referències

modifica
  1. «[https://www.openmp.org/wp-content/uploads/fspec10.pdf OpenMP Fortran Application Program Interface]» (en anglés). OpenMP, 11-02-1998. [Consulta: 15 abril 2019].
  2. «[https://www.openmp.org/wp-content/uploads/cspec10.pdf OpenMP C and C++ Application Program Interface]» (en anglés). OpenMP, 30-10-1998. [Consulta: 15 abril 2019].
  3. «[https://www.openmp.org/wp-content/uploads/fspec20.pdf OpenMP Fortran Application Program Interface Version 2.0, novembre 2000]» (en anglés). OpenMP, 28-10-2000. [Consulta: 15 abril 2019].
  4. «[https://www.openmp.org/wp-content/uploads/cspec20.pdf OpenMP C and C++ Application Program Interface Version 2.0 març 2002]» (en anglés). OpenMP, 01-03-2002. [Consulta: 15 abril 2019].
  5. «[https://www.openmp.org/wp-content/uploads/spec25.pdf OpenMP Application Program Interface Version 2.5 maig 2005]». OpenMP, 01-05-2005.
  6. «[https://www.openmp.org/wp-content/uploads/spec30.pdf OpenMP Application Program Interface Version 3.0 maig 2008]». OpenMP, 01-03-2008.
  7. «[https://www.openmp.org/wp-content/uploads/OpenMP4.0.0.pdf OpenMP Application Program Interface Version 4.0 - juliol 2013]». OpenMP, 01-07-2013.
  8. «[https://www.openmp.org/wp-content/uploads/openmp-4.5.pdf OpenMP Application Programming Interface Version 4.5 novembre 2015]». OpenMP, 01-11-2015.
  9. «[https://www.openmp.org/wp-content/uploads/OpenMP-API-Specification-5.0.pdf OpenMP Application Programming Interface Version 5.0 novembre 2018]». OpenMP, 01-11-2018.
  10. «OpenMP Compilers - OpenMP» (en anglès). [Consulta: 14 juliol 2017].

Enllaços externs

modifica
  • OpenMP - Lloc web oficial
  • GNU GOMP (anglès) Projecte de suport d'OpenMP a C, C++ i Fortran per als compiladors de la GNU Compiler Collection, en funcionament des de la versió GCC 4.2
  • OpenMP a Visual C++ (anglès)