Open MP

Per scrivere programmi paralleli si possono utilizzare le librerie messe a disposizione dal sistema operativo, come i pthread che abbiamo visto nelle scorse puntate (qui), ma sinceramente è un po’ scomodo, non è vero? Troppo di basso livello. Non ci sarebbe un modo più semplice per parallelizzare un programma? Che non mi obblighi a pensare ai thread, alle funzioni, alle variabili locali e globali…

E’ quello che deve essere passato nella mente degli sviluppatori di OpenMP. In pratica l’idea è la seguente: il programmatore scrive il programma come se fosse sequenziale. Poi, individua i pezzi di codice che si potrebbero mandare in parallelo, e li annota. Infine queste annotazioni vengono utilizzate dal compilatore per produrre il codice parallelo. Come al solito, la pratica val più della grammatica, quindi ecco l’esempio di codice parallelo con OpenMP.

#include <pthread.h>
#include <cstdlib>
#include <iostream>

using namespace std;

#ifndef DIM
#define DIM 10000
#endif

int array[DIM][DIM];
int count = 0;

long timespec_sub_us(struct timespec *a, struct timespec *b)
{
    long ret = 0;
    ret = (a->tv_sec - b->tv_sec) * 1000000;
    ret += (a->tv_nsec - b->tv_nsec) / 1000;
    return ret;
}

int main()
{
    cout << "Inizializzazione" << endl;

    for (int i = 0; i<DIM; i++)
        for (int j=0; j<DIM; j++) array[i][j] = rand() % 2;

    cout << "Fine inizializzazione, for parallelo" << endl;

    struct timespec start, stop;
    clock_gettime(CLOCK_REALTIME, &start);

#pragma omp parallel for
    for (int i=0; i<DIM; i++)
        for (int j=0; j<DIM; j++)
            if (array[i][j]) count++;

    clock_gettime(CLOCK_REALTIME, &stop);
    long ut = timespec_sub_us(&stop, &start);

    cout << "Numero totale di non-zeri: " << count << endl;
    cout << "Tempo necessario: " << ut << endl;
}

Per compilare questo programma bisogna scrivere sulla linea di comando la seguente stringa:

g++ openmp.cpp -lgomp -o openmp

E a quel punto basta lanciarlo. L’annotazione di cu isi parlava prima è alla riga 34:

#pragma omp parallel for

praticamente stiamo dicendo al compilatore g++ che il blocco seguente formato dal ciclo for va parallelizzato: ognuna delle iterazioni del ciclo può essere svolta da un thread diverso.

Perché naturalmente sotto il cofano ci sono i thread, solo che voi non li vedete, e con un semplice #pragma il compilatore viene istruito a creare il codice di generazione dei thread. A run-time, il codice generato crea un numero di thread pari al numero di processori.  Nel nostro esempio, su un dual core vengono creati due thread, ognuno dei quali eseguirà il seguente codice:

        for (int j=0; j<DIM; j++)
            if (array[i][j]) count++;

il primo thread eseguirà sui valori di i pari, il secondo sui valori di i dispari. Bello, no? Soprattutto semplice, finalmente! E inoltre scalabile: se siamo su un sistema con 8 core, verranno creati ben 8 thread, ognuno dei quali eseguirà un pezzo diverso del ciclo.

Tutto bene dunque? Beh, non proprio. Il codice che ho scritto in realtà funziona molto, molto male. Ecco i risultati con 1, 2 e 4 processori.

Le barre verticali esprimono (più o meno) la variabilità sulla misura. In pratica, indipendentemente dal numero di processori che utilizzo, il tempo di esecuzione si aggira sempre intorno a 1,3 sec.

Ma com’è possibile? non avevamo parallelizzato? Non è propriamente quello che ci aspettavamo, vero? Il grafico avrebbe dovuto essere simile a quello del post precedente, con un tempo di esecuzione inversamente proporzionale al numero dei processori. Dov’è l’inghippo? La risposta è piuttosto complicata e la rimando alla prossima puntata!

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • juhan  Il 25 giugno 2010 alle 21:14

    Molto semplice, praticamente parallelizzazione gratis, anche se non tanto efficiente.
    Non mi è tanto chiara una cosa: se il numero dei thread sono decisi dal compilatore devi compilarlo su una macchina con il numero di processori uguale a quello su cui girerà o il loro numero avverrà al momento dell’esecuzione?
    Per curiosità liscio che tempi aveva? (OK potrei farlo io…)

  • glipari  Il 25 giugno 2010 alle 22:55

    Mi sono espresso male (la fretta…): non è il compilatore che decide il numero di thread, ma il run-time system. Il compilatore genera solo il codice per creare un numero di thread che dipende dal numero di processori della macchina, e quest’ultimo viene rilevato a run-time!

    In realtà, dal grafico si vede che in questo caso la parellizzazione non ha funzionato: ti anticipo che il problema è dovuto al fatto che ho usato un’unica variabile count. Il motivo esatto è complicato e lo spiego la prossima volta.

    Bye!

  • D  Il 26 giugno 2010 alle 12:07

    Invalidazione della cache?

    • glipari  Il 27 giugno 2010 alle 11:03

      Si certo! Si forma un ping-pong continuo fra le chache di livello L1 dei vari core. Per non parlare della possibilità di creare inconsistenza…

  • Gabry  Il 18 maggio 2011 alle 18:34

    Scusa la mia ignoranza. Se mi trovo su una macchina con 8 processori come faccio a variare il numero di processori utilizzati?
    Dalla teoria di Openmp ho letto di settare la variabile mediante export MP_NUM_THREADS, prima di eseguire il programma compilato, ma non provoca nessun effetto, in quanto il programma mi informa che sono sempre 8 i threads in esecuzione.

    • glipari  Il 18 maggio 2011 alle 19:45

      Ciao,
      di default il numero di thread viene posto uguale al numero di processori. Se vuoi variarlo, puoi chiamare:

      omp_set_num_threads(n);

      dove n è il numero di threads. Oppure, in una clausa #parallel, puoi usare il parametro num_threads, così:

      #pragma omp parallel num_threads(n)
      for (…) …

      Prova e poi dimmi se ti funziona! (informazioni prese dal manuale, oppure da qui). Riguardo alla variabile di ambiente, dovrebbe funzionare, ma non so esattamente cosa fai, quindi non ho idea di dove sia l’errore.

Trackback

  • Un po’ di coerenza « Ok, panico su 16 luglio 2010 alle 23:54

    […] po’ di coerenza Nello scorso post ho presentato OpenMP, un’estensione del C per scrivere programmi paralleli in maniera […]

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: