OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge

Pagina creata da Beatrice Piccolo
 
CONTINUA A LEGGERE
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
Introduzione al calcolo parallelo con
OpenMP

                                        Cristiano Padrin ( c.padrin@cineca.it)
                                              Neva Besker (n.besker@cineca.it)
                 Roma, 17 Luglio 2017
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
Agenda

   MPI vs OpenMP
        Svantaggi di MPI
        Cos’è OpenMP
   Informazioni storiche
        OpenMP: since 1997
   Concetti preliminari
        Architetture a memoria condivisa
        Processi e threads: il multi-threading
        Gestione della memoria in OpenMP
        Modello di esecuzione: Fork & Join
   Elementi di base
        Direttive OpenMP
        Direttive OpenMP: clausole
        Variabili d’ambiente di OpenMP
        Funzioni a run-time di OpenMP
        Compilazione condizionale
        Compilare con OpenMP

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
Agenda

   Costrutti Principali
       Costrutto parallel
       Costrutto parallel: Hello world
       parallel: variabili shared e private
       Data races e costrutto critical
       Costrutto critical: esempio
       Prime considerazioni
   Costrutti di worksharing
       Definizione
       Costrutto loop
       Costrutto loop: sintassi
       Costrutto loop: esempio C/C++
       Costrutto loop: esempio Fortran
       Clausola collapse
       Costrutto sections
       Costrutto sections: sintassi
       Costrutto loop: esempio C/C++
       Costrutto loop: esempio Fortran
       Costrutto single
       Costrutto workshare

                             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
Agenda

   Scheduling
         Definizione
         static
         dynamic
         guided
         Ulteriori attributi: auto e runtime
         Un esperimento
   Clausole e attributi di data-sharing
         defautl
         shared e private
         reduction
   Sincronizzazione
         Costrutto barrier e clausola nowait
         Costrutto ordered
         Costrutto atomic
         Costrutto atomic: esempi
   Altri strumenti
         Costrutto master
         Timing

                              Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
MPI vs OpenMP

           Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
MPI vs OpenMP – Svantaggi di MPI

        Ogni processo MPI può accedere solamente alla sua memoria locale:
          i dati da condividere tra i processi devono essere scambiati attraverso
          esplicite comunicazioni inter-process (messaggi);
          è responsabilità del programmatore progettare ed implementare lo
          scambio dei dati tra i processi.
       Non è possibile adottare una strategia incrementale di parallelizzazione:
          la struttura di comunicazione dell’intero programma deve essere
          implementata.
       Le comunicazioni hanno un costo (in prestazioni).
       È difficile avere un’unica versione del codice che vada bene sia per
       l’esecuzione seriale che parallela MPI:
          è necessario aggiungere delle variabili;
          è necessario gestire la corrispondenza tra le variabili locali e la struttura
          globale dei dati.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
MPI vs OpenMP – Cos’è OpenMP

      OpenMP è (de-facto) una standard API (Application Program Interface) per
      scrivere applicazioni parallele a memoria condivisa in C, C++ e Fortran.
      Sfrutta:
        direttive di compilazione;
        variabili d’ambiente;
        funzioni a run-time.
      I workers che lavorano in parallelo (threads) cooperano attraverso la
      memoria condivisa (shared memory):
        accessi diretti alla memoria invece di espliciti messaggi;
        modello locale di parallelizzazione del codice seriale.
      Consente una strategia incrementale di parallelizzazione.
      Open specifications for Multi Processing è mantenuto standard dal
      OpenMP Architecture Review Board (http://www.openmp.org/).

                               Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Informazioni storiche

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Informazioni storiche – OpenMP: since 1997

   OpenMP nasce dalla
   necessità di uniformare
   soluzioni proprietarie.
   Nel Novembre del 2015
   sono state presentate le
   specifiche per la versione
   4.5.
   Nel Novembre del 2016
   sono stati pubblicati i primi
   esempi per la versione 4.5.
   In futuro sarà disponibile la
   verisione 5.0.
   http://www.openmp.org/speci
   fications/

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari - Architetture a memoria condivisa

       Tutti i processi possono accedere all’intera memoria principale.

                      NUMA                                           UMA
           (Non-Uniform Memory Access)                      (Uniform Memory Access)
        il tempo di accesso alle memoria non è          il tempo di accesso alle memoria è
        uniforme                                        uniforme

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari - Processi e threads: il multi-threading

        Un processo è l’istanza di un programma.
        Alcune informazioni contenute in un
        processo sono:
          Data
            variabili globali;
          Text
            codice macchina;
          Stack
            variabili locali;
          Program counter (PC)
            un puntatore alle istruzioni da
            eseguire.
        Un processo può essere suddiviso in
        threads (multi-threading).

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari - Processi e threads: il multi-threading

        Un processo contiene diversi flussi di
        esecuzione concorrenti (threads):
          ogni thread ha il suo program counter;
          ogni thread ha la sua stack privata:
            variabili locali al thread;
          le istruzioni eseguite da un thread
          possono accedere indistintamente a:
            memoria globale del processo (data);
            stack locale del thread;
        Multi-threading!

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari – Gestione della memoria in OpenMP

       Tutti i threads hanno accesso alla stessa
       memoria condivisa globalmente (shared
       memory):
         il trasferimento di dati avviene
         attraverso la memoria condivisa ed è
         trasparente a tutta l’applicazione;
         ogni thread vede le operazioni che gli
         altri threads compiono quando
         accedono alla memoria condivisa.
       Un dato residente in un’area di memoria
       privata è accessibile solo dal thread
       proprietario della suddetta area di
       memoria (private memory):
         nessun thread può accedere all’area di
         memoria privata di un altro thread, né
         tantomeno vedere eventuali modifiche
         ai dati in essa contenuti.

                                 Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Concetti preliminari - Modello di esecuzione: Fork & Join

       Fork & Join è il modello di esecuzione di OpenMP:
        ogni programma OpenMP inizia la sua esecuzione con un singolo thread
        (Master thread) che processa il programma in seriale;
        all’inizio di una regione parallela il Master thread crea un gruppo di threads
        composto da se stesso ed un insieme di altri threads (Fork);
        il gruppo di threads processa in parallelo il codice contenuto nella regione
        parallela (modello SPMD - Single Program Multiple Data);
        alla fine della regione parallela il gruppo di threads cessa di esistere (Join)
        e solo il Master thread continua a processare il programma (in seriale).

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Direttive OpenMP

       OpenMP si basa su una programmazione parallela per direttive.
       Le direttive sono introdotte con una sintassi distinta a seconda del
       linguaggio di programmazione in cui è scritto il codice (seriale):
          #pragma omp           direttiva  → (C/C++);
          !$OMP                 direttiva  → (Fortran - free format);
          c$OMP op *$OMP direttiva         → (Fortran - fixed format).
       Questa sintassi marca i blocchi di codice da parallelizzare:
          istruisce il compilatore su come eseguire in parallelo il blocco di codice
          marcato.
       Il codice seriale coesiste con il codice parallelo:
          una compilazione seriale ignora le direttive;
          una compilazione con supporto OpenMP tiene conto delle direttive.

                                 Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Direttive OpenMP: clausole

       Una direttiva può essere caratterizzata con delle clausole:
        sintassi →  [ clausola[ attributo ] … ]
       Una clausola fornisce informazioni aggiuntive (attributo) alle direttive:
        gestione delle variabili:
           cosa è condiviso tra i threads (condizione di default);
           quali variabili sono private per ogni thread;
           come inizializzare le variabili private;
           cosa è default;
        controllo dell’esecuzione:
           quanti threads sono nel gruppo;
           come distribuire il lavoro.
       ATTENZIONE: le clausole possono alterare la semantica del codice;
        il codice può essere corretto nella sua versione seriale ma non in quella
        parallela (o viceversa).

                                 Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Variabili d’ambiente di OpenMP

       OpenMP utilizza delle variabili d’ambiente per gestire il parallelismo:
         OMP_NUM_THREADS                          → fissa il numero di threads;
         OMP_SCHEDULE “schedule[,chunk]” → determina lo schema dello scheduling
                                                    per le iterazioni;
         OMP_STACKSIZE “size [B|K|M|G]”           → indica la taglia dello stack per i threads;
         OMP_DYNAMIC {true|false}                 → attiva/disattiva l’adjustment dinamico
                                                     del numero di threads per ottimizzare
                                                     l’uso delle risorse;
         OMP_PROC_BIND {true|false}               → lega i threads ai processori;
         OMP_NESTED {true|false}                  → attiva/disattiva il parallelismo annidato;
         …
       Per impostare le variabili d’ambiente (esempio):
         in csh/tcsh → setenv OMP_NUM_THREADS 4;
         in sh/bash → export OMP_NUM_THREADS=4.

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Funzioni a run-time di OpenMP

       OpenMP utilizza delle funzioni a run-time per interrogare/dichiarare
       specifiche caratteristiche o configurazioni:
         omp_get_thread_num ( )            → restituisce il numero ID del thread (0
                                              per il Master thread);
         omp_get_num_threads ( )           → restituisce il numero complessivo dei
                                              threads;
         omp_set_num_threads (int n) → imposta il numero complessivo dei
                                              threads;
         omp_get_wtime ( )                 → restituisce il tempo di vita del
                                              processore (su cui si trova il thread);
         omp_get_wtick ( )                 → restituisce la precisione (in secondi) del
                                              timer;
         …
       Per poter utilizzare queste funzioni:
         in C/C++ → #include 
         in Fortran → use omp_lib (or include ‘omp_lib.h’)

                                 Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Compilazione condizionale con OpenMP

       Per prevenire eventuali dipendenze con le librerie OpenMP si possono
       utilizzare le direttive di pre-processing con la macro _OPENMP:
         le direttive possono essere usate sia in C/C++ che in Fortran;
         in Fortran vengono riconosciute in fase di pre-processing anche:
            !$            in free format;
            c$ op *$      in fixed format;
         la macro _OPENMP è predefinita dallo standard.
       C/C++/Fortran
       #ifdef _OPENMP
        < code >
       #else
        < code >
       #endif

       Fortran
       !$ < instruction >, _OPENMP

                                Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Elementi di base – Compilare con OpenMP

       I compilatori che supportano OpenMP interpretano le direttive solo se sono
       utilizzati con un’opzione di compilazione:
          GNU          -fopenmp        per Linux, Solaris, AIX, MacOSX, Windows;
          IBM          -qsmp=omp       per Linux, AIX, Windows;
          PGI          -mp             ;
          Sun          -xopenmp        per Linux, Solaris;
          Intel        -qopenmp        per Linux, MacOSX, Windows.
       La maggior parte dei compilatori forniscono informazioni utili quando
       vengono abilitate opzioni per warning ulteriori a quelli di default, o opzioni di
       creazione dei report.

                                Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali

              Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali – Costrutto parallel

        Un costrutto è l’entità lessicale con la quale viene applicata una direttiva di
        esecuzione.
        Una regione è l’entità dinamica alla quale viene applicata una direttiva di
        esecuzione.
        Una regione parallela è un blocco di codice esguito da tutti i threads.
        Il costrutto parallel crea la regione parallela.

        C/C++
        #pragma omp parallel
        {
          < code >
        }

        Fortran
        !$omp parallel
         < code >
        !$omp end parallel

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali – Costrutto parallel: Hello world

        C/C++
        #include 
        int main ()
        {
          #pragma omp parallel
          {
              printf(“Hello world!\n”);
          }
          return 0;
        }

        Fortran
        Program Hello
        !$omp parallel
               print *, “Hello world!”
        !$omp end parallel
        end program Hello

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali – parallel: variabili shared e private

        Dentro una regione parallela, le variabili di un programma seriale possono
        essere essenzialmente:
          condivise → esiste solo una istanza del dato:
            il dato è accessibile da tutti i threads;
            i threads posso leggere e scrivere il dato simultaneamente;
            tutti i threads accedono allo stesso indirizzo di memoria;
          private        → ogni thread ha un copia del dato:
            nessun altro thread può accedere a questo dato;
            le variazioni del dato sono visibili solo al thread proprietario;
            i valori sono non definiti in ingresso ed in uscita (nella/dalla regione).
        ATTENZIONE: le variabili sono shared di default;
          aggiungendo la clausola default(none) al costrutto parallel,
          nessuna variabile sarà considerata con un default, e lo scope si dovrà
          dichiarare per tutte le variabili esplicitamente.

                                    Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali – Data races e costrutto critical

        Un data race si verifica quando due o più threads accedono alla stessa
        locazione di memoria (shared):
          in modalità asincrona e ...
          … almeno uno degli accessi è di scrittura/salvataggio.
        Se si verifica un data race, i valori dei risultati non sono definiti.
        Un construtto per sincronizzare l’accesso in scrittura/salvataggio del dato
        condiviso è critical:
          il blocco di codice sul quale agisce il critical viene eseguito da un solo
          thread alla volta;
          questa sincronizzazione previene accessi simultanei al dato condiviso.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti principali – Costrutto critical: esempio

        C/C++
        sum = 0;
         #pragma omp parallel private(i, MyThId, psum)
         {
             MyThId = omp_get_thread_num();
             NumThs = omp_get_num_threads();
             int psum = 0;
             for (i=MyThId*N/NumThs; i
Costrutti principali – Prime considerazioni

        Il costrutto parallel ha una barriera implicita in uscita:
           i threads devono aspettare che tutti abbiano completato le loro attività nel
           blocco di codice sul quale incide il costrutto.
        Il costrutto critical non ha barriere implicite, ma si limita a sincronizzare i
        threads.
        Essenzialmente, per una parallelizzazione con OpenMP, è sufficiente quanto
        visto finora:
           il costrutto parallel;
           il costrutto critical;
           la funzione omp_get_thread_num();
           la funzione omp_get_num_threads().
        Tuttavia lo scopo di una programmazione parallela è quello di distribuire
        quanto più lavoro seriale tra i threads, cercando di mantenere il controllo di
        come avvenga questa distribuzione.
        Per automatizzare questa distribuzione si possono utilizzare i construtti di
        worksharing.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Definizione

       Un costrutto di worksharing distribuisce l’esecuzione della porzione di
       regione parallela su tutti i threads che la devono processare.
       I costrutti di worksharing hanno una barriera implicita in uscita:
          la clausola nowait permette di rimuovere questa barriera.
       OpenMP definisce i seguenti costrutti come costrutti di worksharing:
          for/do          (costrutto loop);
          sections;
          single;
          workshare       (solo per Fortran).

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto loop

       Le iterazioni di un ciclo vengono distribuite tra i threads che già esistono nel gruppo.
       L’indice (variabile di iterazione) del ciclo (più esterno, nel caso di cicli annidati) ha
       come attributo di data-sharing private di default:
           il ciclo interno è eseguito in modo sequenziale da ogni thread;
           gli indici dei cicli interni sono:
              private         in Fortran (di default);
              shared          in C/C++ (di default).
       Requisito fondamentale per la parallelizzazione di un ciclo: non devono sussistere
       dipendenze tra gli indici di ciclo.
        Il costrutto loop accetta come clausole:
           private/firstprivate/lastprivate( lista );
           reduction( op:lista );
           schedule( kind,chunk );
           collapse( n );
           ordered;
           nowait          (in Fortran la calusola viene inserita in uscita).

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto loop: sintassi

        C/C++
         #pragma omp for [… clauses …]
             for (i=0; i
Costrutti di worksharing – Costrutto loop: esempio C/C++

       C/C++
       int main( )
       {
             int i, n = 10;
             int a[n], b[n], c[n];
             …
       #pragma omp parallel
         {
         #pragma omp for
            for (i=0; i
Costrutti di worksharing – Costrutto loop: esempio Fortran

       Fortran
       program do_example
       integer, parameter :: n = 10
       integer :: i, a(n), b(n), c(n)
       !$omp parallel
       !$omp do
       do i = 1, n
         a(i) = i
         b(i) = i
         c(i) = 0
       end do
       !$omp end do
       !$omp do
       do i = 1, n
         c(i) = a(i) + b(i)
       end do
       !$omp end do
       !$omp end parallel
       …
       end program do_example

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Clausola collapse

       In un ciclo annidato, i threads parallelizzano l’iterazione del solo ciclo esterno,
       mentre il ciclo interno viene eseguito sequenzialmente da ogni thread.
       La calusola collapse permette la parallelizzazione di cicli perfettamente
       annidati:
         la clausola indica quanti cicli (attributo) devono collassare;
         il compilatore costruisce un singolo ciclo e lo parallelizza.
        C/C++
        #pragma omp for collapse(2) private(j)
            for (i=0; i
Costrutti di worksharing – Costrutto sections

       Distribuisce tra i threads blocchi strutturati di codice:
         ogni thread riceve in carico una section;
         quando un thread termina l’esecuzione del blocco strutturato che gli è stato
         assegnato, gli viene assegnata un’altra section;
         se non ci sono più blocchi da assegnare, i threads attendono il completamento
         delle esecuzioni dei threads ancora impegnati nel calcolo.
       Il costrutto sections accetta come clausole:
         private/firstprivate/lastprivate( lista );
         reduction( op:lista );
         nowait             (in Fortran la calusola viene inserita in uscita).

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto sections: sintassi

        C/C++
         #pragma omp sections [… clauses …]
          {
            #pragma omp section
            {structured block}
            #pragma omp section
            {structured block}
            …
          }

        Fortran
        !$omp sections [… clauses …]
        !$omp section
            structured block
        !$omp end section
        !$omp section
            structured block
        !$omp end section
            …
        !$omp end do [nowait]

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto sections: esempio C/C++

       C/C++
       void X_AXIS( );
       void Y_AXIS( );
       void Z_AXIS( );

       void sect_example( )
       {
       #pragma omp parallel sections
         {
           #pragma omp section
             X_AXIS( );
           #pragma omp section
             Y_AXIS( );
           #pragma omp section
             Z_AXIS( );
         }
       }

                                 Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto sections: esempio Fortran

       Fortran
       subroutine sect_example( )
       !$omp parallel sections

           !$omp section
           call x_axis()
           !$omp end section

           !$omp section
           call y_axis()
           !$omp end section

           !$omp section
           call z_axis()
           !$omp end section

       !$omp end parallel sections
       end subroutine sect_example

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto single

       Il primo thread che raggiunge il costrutto, esegue il blocco di codice associato:
          gli altri threads attendono il completamento dell’esecuzione del thread
          incaricato del calcolo.
        Il costrutto single accetta come clausole:
          private/firstprivate( lista );
          copyrivate( lista ) (in Fortran la calusola viene inserita in uscita);
         nowait                 (in Fortran la calusola viene inserita in uscita).
        C/C++
         #pragma omp single [… clauses …]
            {structured block}

        Fortran
        !$omp single [private][firstprivate]
            structured block
        !$omp end single [copyprivate][nowait]

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Costrutti di worksharing – Costrutto workshare

       Il blocco di codice associato viene suddiviso in unità di lavoro che, a loro volta,
       vengono assegnate ai threads in modo che ogni unità venga eseguita da un
       thread solamente una volta.
        Il costrutto workshare:
          è supportato solamente per il Fortran;
          accetta come clausola solo nowait (in uscita);
         viene usato per parallelizzare l’array syntax.

        Fortran (only)
        !$omp workshare
            structured block
        !$omp end workshare [nowait]

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – Definizione

       In OpenMP, è lo schema con il quale i threads si distribuiscono il lavoro di
       un’iterazione di un ciclo.
       La distribuzione avviene:
         dividendo l’intervallo dell’iterazione in sottoinsiemi contigui non vuoti,
         chiamati chunks;
         impostando uno schema di assegnazione dei chunks tra i threads.
       OpenMP consente di scegliere tra i seguenti schemi di assegnazione dei
       chunks:
         static;
         dynamic;
         guided.

                               Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – static

       Le iterazioni sono divise in chunks
       di taglia chunk, e vengono
       assegnati ai threads secondo un
       criterio round-robin basato sul
       numero identificativo del thread.
       È lo schema di default:
         il valore assunto da chunk di
         default è (approssimativamente)
         pari al rapporto tra il valore
         massimo dell’iterazione ed il
         numero complessivo di threads;
            chunk ? Niter/Nthreads.

                      In figura:
                      Niter                 = 36;
                      Nthreads              = 4;
                      chunk (di default)    = 9;
                      chunk (scelto)        = 3.

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – dynamic

       I chunks vengono assegnati (uno
       alla volta) ai threads liberi.
       Ogni thread esegue chunk
       iterazioni e successivamente gli
       vengono assegnate altre chunk
       iterazioni, fino a quando tutti i
       chunks non sono stati assegnati.
       Per lo schema dynamic, di default,
       il valore assunto da chunk è 1.

                       In figura:
                       Niter                 = 36;
                       Nthreads              = 4;
                       chunk (di default)    = 1.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – guided

       I chunks vengono assegnati (uno
       alla volta) ai threads liberi.
       Ogni thread esegue il numero di
       iterazioni assegnate e quando
       termina il calcolo ne riceve altre,
       fino a quando tutti i chunks non
       sono stati assegnati.
       Il numero di iterazioni dei chunks,
       ad ogni assegnazione, tende a
       decrescere fino al valore di chunk.
       Per lo schema guided, il valore di
       default assunto da chunk è 1.

                      In figura:
                      Niter                 = 36;
                      Nthreads              = 4;
                      chunk (di default)    = 1.

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – Ulteriori attributi auto e runtime

        auto: la scelta dello schema di assegnazione è delegata al compilatore e/o al
        sistema a run-time.
        runtime: la scelta dello schema di assegnazione viene stabilita a run-time
        impostando la variabile d’ambiente OMP_SCHEDULE ed aggiungendo la clausola
        schedule al costrutto loop:
          lo schema di assegnazione può essere modificato senza ricompilare,
          semplicemente impostando nuovamente la variabile d’ambiente.

       runtime (example) - Environment
           setenv OMP_SCHEDULE “dynamic,50”                 [ csh/tcsh ]
           export OMP_SCHEDULE=“dynamic,50”                 [ sh/bash ]

       runtime (example) - C/C++
       #pragma omp parallel for schedule(runtime)

       runtime (example) - Fortran
       !$omp parallel do schedule(runtime)

                                    Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Scheduling – Un esperimento

            guided
            dynamic
            static

                      I tre schemi per un ciclo di 1000 iterazioni con 4 threads

                                     Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Clausole e attributi di data-sharing

              Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Clausole e attributi di data-sharing – default

        Quando la clausola defautl viene aggiunta al costrutto parallel, gli attributi di
        data-sharing vengono implicitamente determinati.
        La clausola default determina implicitamente gli attributi di tutte le variabili/oggetti:
          default(none|shared)                                           (C/C++);
          default(none|shared|private|firstprivate) (Fortran).
        Quando la calusola non è utilizzata, le variabili hanno attributi predeterminati:
          private:
            la variabile di iterazione del ciclo for esterno (C/C++);
            le variabili di iterazione dei cicli do (Fortran);
            le variabili con un’allocazione automatica della memoria che sono dichiarate in
            uno scope interno ad un costrutto;
          shared:
            gli objects con un’allocazione dinamica delle memoria;
            le variabili con un’allocazione statica della memoria che sono dichiarate in uno
            scope interno ad un costrutto;
            … quasi tutto il resto.

                                    Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Clausole e attributi di data-sharing – shared e private

        Gli attributi di data-sharing possono essere esplicitamente determinati
        referenziandoli in un costrutto ed elencandoli in una attribute-clause:
          shared(list): esiste una sola istanza della variabile in elenco, ed è
          accessibile da tutti i threads;
          private(list): ogni thread ha una copia della variabile in elenco;
          firstprivate(list): ogni thread ha una copia della variabile in elenco;
            la copia della variabile in elenco è inizializzata con il valore assunto dalla
            variabile originale immediatamente prima dell’ingresso nel costrutto;
          lastprivate(list): ogni thread ha una copia della variabile in elenco;
            il valore assunto dalla variabile originale viene determinato al termine
            dell’elaborazione di un costrutto di worksharing, e corrisponde al valore
            (assunto dalla copia) calcolato per l’ultimo valore della variabile di
            iterazione o al termine dell’ultimo costrutto section.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Clausole e attributi di data-sharing – reduction

        In OpenMP, la clausola reduction(op:list) permette la
        parallelizzazione di alcune operazioni di riduzione;
          reduction­identifier (op):
            +,       –,      *,       max, min        (C/C++/Fortran);
            &,       |,      ^,       &&,      ||     (C/C++);
            .and., .or.,     .eqv., .neqv.            (Fortran);
            iand, ior,       ieor                     (Fortran).
        Per ogni oggetto della list viene creata una copia privata in ogni task
        implicito.
        La copia locale viene inizializzata in modo appropriato all’identificatore, ad
        esempio:
          +, – → 0;
          *        → 1.

                                   Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Clausole e attributi di data-sharing – reduction

        Il valore della variabile originale viene aggiornato con i valori delle copie
        private, utilizzando l’identificatore specificato, dopo la fine della regione.
        Le variabili sulle quali agisce la reduction devono essere shared.
        ATTENZIONE: il valore ottentuo con una riduzione è indefinito dall’istante
        in cui il primo thread raggiunge la clausola fino al completamento
        dell’operzione.

        C/C++
        #pragma omp parallel for reduction(+:add)
            for (i=0; i
Sincronizzazione

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Sincronizzazione – Costrutto barrier e clausola nowiat

       In una regione parallela i thread lavorano in modo asincrono tra loro, fino a quando
       non incontrano una barriera:
          ogni thread arrivato alla barriera si mette in attesa degli altri, e riparte solo quando
          tutti i threads hanno raggiunto la stessa barriera (sincronizzazione);
          la barriera garantisce che l’intero codice (fino a quel punto) sia stato eseguito.
       Ad esempio, i costrutti parallel e di worksharing hanno una barriera implicita:
          la barriera può essere rimossa con la clausola nowait.
       Il costrutto barrier consente di inserire nel codice una barriera esplicita.

        C/C++
        #pragma omp barrier

        Fortran
        !$omp barrier

                                    Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Sincronizzazione – Costrutto ordered

       Il costrutto ordered consente di eseguire in modalità sequenziale un blocco
       di codice all’interno di un ciclo:
          quando il thread incaricato di eseguire la prima iterazione raggiunge una
          regione ordinata, vi accede senza aspettare;
          il thread incaricato di eseguire un’iterazione successiva non entra nella
          regione ordinata fino al completamento dell’esecuzione di tutte le regioni
          ordinate afferenti alle iterazioni precedenti (sincronizzazione).
       ATTENZIONE:
          il costrutto ordered deve essere introdotto dalla omonima clausola
          dichiarata nella direttiva di ciclo;
          si può inserire un solo costrutto ordered per ciclo:
             un’iterazione non può eseguire più di una regione ordinata.

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Sincronizzazione – Costrutto ordered

    C/C++                                         Fortran
    #pragma omp parallel                          !$omp parallel do ordered
     {                                                 do i = 1, n
       #pragma omp for ordered                            …
          for (i=0; i
Sincronizzazione – Costrutto atomic

       Il costrutto atomic viene applicato solamente allo statement che deve
       aggiornare il valore di una variabile:
          garantisce che nessun altro thread aggiorni la variabile tra la lettura e la
          scrittura (sincronizzazione).
       È una forma particolare e più leggera del costrutto critical:
          il costrutto critical agisce solo se due o più threads accedono allo
          stesso indirizzo di memoria e serializza le sole operazioni di read/write.
       ATTENZIONE: il costrutto atomic sviluppato per C/C++ e quello sviluppato
       per Fortran differiscono (fare riferimento alle specifiche di OpenMP).

    C/C++                                         Fortran
    #pragma omp atomic [clause]                   !$omp atomic [clause]
      {statement}                                   statement

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Sincronizzazione – Costrutto atomic: esempi

    C/C++                                         Fortran
    #pragma omp atomic update                     !$omp atomic update
    x += n*mass;   default update                 x = x + n*mass   default update
                                                  !$omp end atomic

    #pragma omp atomic read                       !$omp atomic read
    v = x;         read atomically                v = x            read atomically
                                                  !$omp end atomic

    #pragma omp atomic write                      !omp atomic write
    x = n*mass;    write atomically               x = n*mass       write atomically
                                                  !$omp end atomic

    #pragma omp atomic capture                    !omp atomic capture
    v = x++;       capture x in v and             v = x            capture x in v and
                   update x atomically            x = x + 1        update x atomically
                                                  !$omp end atomic

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Altri strumenti

             Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Altri strumenti – Costrutto master

       In costrutto master demanda al Master thread l’esecuzione della regione di codice
       sulla quale agisce:
         non ha barriere implicite né in ingresso né in uscita;
         non è un costrutto di worksharing;
         gli altri threads ignorano la regione di codice interessata dal costrutto, e
         proseguono l’esecuzione delle regioni successive.

          C/C++
          #pragma omp master
             {code–block}

          Fortran
          !$omp master
              code–block
          !$omp end master

                                     Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Altri strumenti – Timing: omp_get_wtime

       La funzione di run-time omp_get_wtime è un utile strumento per valutare quanto il
       codice parallelo sia più veloce del codice seriale (speed-up), e quale sia l’efficienza.

  C/C++                                               Fortran
  #include                                     program …
  …                                                         use omp_lib
  int main( )                                               …
  {                                                         real(kind(1.d0)) :: time1, time2
      …                                                     …
      double time2;                                         time1 = omp_get_wtime()
      …                                                     !$omp parallel
      double time1 = omp_get_wtime();                         … something …
      #pragma omp parallel                                  !$omp end parallel
      {                                                     time2 = omp_get_wtime()
        … something …                                       write(*,*) 'Elapsed time (s) =', &
      }                                                                 time2­time1
      time2 = omp_get_wtime() ­ time1;                      …
      printf("Elapsed time (s) = %.6lf\n", time2);    end program …
      …
  }

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Conclusioni

              Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Conclusioni – Cosa non è stato trattato in questo corso

       Il costrutto task:
          introdotto nella release 3.0, non è un costrutto di worksharing;
          crea un nuovo task ma non nuovi threads (si può considerare parallel come il
          primo e più importante task):
             può essere rinviato fino a quando un thread non è disponibile per l’escuzione;
             l’ambiente data viene costruito nell’istante in cui viene creato il nuovo task;
             le variabili ereditano gli attributi di data-sharing ma le private diventano
             firstprivate;
          permette di parallelizzare problemi irregolari:
             unbounded loop, algoritmi ricorsivi, schemi producer/consumer, raffinamento di
             griglie adattative, …

    C/C++                                         Fortran
    #pragma omp task [clause]                     !$omp task [clause]
    {                                               Structured block
      {structured block}                          !$omp end task
    }

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Conclusioni – Cosa non è stato trattato in questo corso

       La direttiva threadprivate:
         è una direttiva dichiarativa;
         viene usata per creare copie private di:
            file-scope, namespace-scope o variabili static in C/C++;
            blocchi common o variabili definite nei module in Fortran;
         rispetta la dichiarazione delle variabili nella stessa program unit;
         i dati iniziali sono indefiniti, a meno che non sia utilizzata la clausola
         copyin.

    C/C++                                         Fortran
    #pragma omp threadprivate (list)              !$omp threadprivate (list)

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Conclusioni – Cosa non è stato trattato in questo corso

       Il caso in cui le direttive diventano orphaned:
          sono direttive che possono trovarsi al di fuori di una regione parallel:
          le specifiche di OpenMP non impongono che i costrutti di worksharing e le
          direttive di sincronizzazione debbano essere contenute all’interno di un
          costrutto parallel;
          sono ignorate se chiamate in una regione seriale ma gli eventuali attributi
          di data-sharing verranno applicati.
       Le funzioni lock.
       Le direttive flush, simd, cancel, target, …
       Le calusole copyin e copyprivate.

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Conclusioni – Doverosi ringraziamenti

       Questo materiale è stato realizzato e modificato negli anni da diversi colleghi
       e amici di CINECA SCAI:
           Neva Besker, Marco Comparato, Federico Massaioli, Piero Lanucara,
           Marco Rorro, Vittorio Ruggiero, Francesco Salvadore, Claudia Truini, …
       Molte persone sono coinvolte nello sviluppo di OpenMP:
         Ruud van der Pas, Alejandro Duran, Bronis de Supinski, Tim Mattson e
         Larry Meadows… e molti altri ancora!

                                  Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Roma, 17 Luglio 2017 - Introduzione al calcolo parallelo con OpenMP
Puoi anche leggere