OpenMP Introduzione al calcolo parallelo con - Cristiano Padrin ( ) - HPC-Forge
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
Introduzione al calcolo parallelo con OpenMP Cristiano Padrin ( c.padrin@cineca.it) Neva Besker (n.besker@cineca.it) Roma, 17 Luglio 2017
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
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
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
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; reductionidentifier (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) =', & } time2time1 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