Università degli Studi di Napoli Federico II Scuola Politecnica e delle Scienze di Base Area Didattica Scienze MM.FF.NN. Master di I° Livello in Tecnologie per il CAlcolo Scientifico ad Alte Prestazioni - CASAP Tesi di Master Valutazione delle prestazioni di algoritmi paralleli per il calcolo scientifico in ambienti con CPU multi-core: Presentazione di un caso di studio Relatori Prof. Marco Lapegna Anno Accademico 2012-2013 Candidato Dott. Luigi Perillo matr. Z62/000007 Indice Generale Introduzione...............................................................................................................5 1 Parallelismo...........................................................................................................7 1.1 Architettura di Memoria.................................................................................8 1.2 Organizzazione della Memoria.....................................................................15 1.3 Modelli di programmazione parallela...........................................................20 1.4 Valutare algoritmi paralleli...........................................................................23 2 Problema affrontato.............................................................................................27 2.1 Descrizione Algoritmo..................................................................................30 2.2 Strategia di distribuzione dei dati.................................................................31 2.3 Test performance e stima errori....................................................................33 2.3.1 Test efficienza algoritmo.......................................................................34 2.3.2 Test stima errore....................................................................................40 3 Conclusioni.........................................................................................................43 Bibliografia..............................................................................................................45 Luigi Perillo Z62/000007 Pagina 2 di 45 Indice delle illustrazioni Illustrazione 1: Schema Tassonomia di Flynn............................................................9 Illustrazione 2: Schema architettura SISD...............................................................10 Illustrazione 3: Schema architettura SIMD..............................................................12 Illustrazione 4: Schema architettura MISD..............................................................13 Illustrazione 5: Schema architettura MIMD............................................................14 Illustrazione 6: Architettura a memoria condivisa...................................................16 Illustrazione 7: Architettura a memoria distribuita...................................................17 Illustrazione 8: Architettura di Memoria Ibrida Condivisa-Distribuita....................19 Illustrazione 9: Griglia di comunicazione tra nodi...................................................29 Illustrazione 10: Grafico Speed Up, Redist 0...........................................................35 Illustrazione 11: Grafico Speed Up, Redist 1...........................................................36 Illustrazione 12: Grafico Efficienza Media, Redist 0...............................................38 Illustrazione 13: Grafico Efficienza Media, Redist 1...............................................39 Illustrazione 14: Grafico Errore stimato. Redist 0 (scala logaritmica).....................41 Illustrazione 15: Grafico Errore stimato. Redist 1 (scala logaritmica).....................42 Luigi Perillo Z62/000007 Pagina 3 di 45 Indice delle tabelle Tabella 1: Tempi di esecuzione. Redist 0.................................................................34 Tabella 2: Tempi di esecuzione. Redist 1.................................................................34 Tabella 3: Speed Up medio su 3 esecuzioni. Redist 0..............................................35 Tabella 4: Speed Up medio su 3 esecuzioni. Redist 1..............................................36 Tabella 5: Efficienza media su 3 esecuzioni. Redist 0.............................................37 Tabella 6: Efficienza media su 3 esecuzioni. Redist 1.............................................38 Tabella 7: Errori Elaborazione. Redist 0..................................................................40 Tabella 8: Errori elaborazione. Redist 1...................................................................41 Luigi Perillo Z62/000007 Pagina 4 di 45 Introduzione La crescente potenza di calcolo messa a disposizione dai moderni calcolatori ha permesso di fronteggiare problemi numerici dalla complessità sempre maggiore in tempi relativamente brevi. In precedenza si puntava a migliorare le prestazioni dei processori, aumentando la frequenza di clock, portando però in questo caso ad un consumo eccessivo dei processori. Per questo motivo, l'attenzione si è spostata verso lo sviluppo di processori multicore i quali a frequenze di clock decisamente ridotte permettono di avere prestazioni simili, in alcuni casi anche superiori, ai più potenti processori single-core. Lo scopo del lavoro svolto, è stato quello di approfondire una metodologia di valutazione di funzioni per il calcolo scientifico, in ambiente computazionale basato su CPU multi-core. Negli ultimi trent'anni, diverse routine efficienti sono stati sviluppati per la soluzione del problema sulla CPU tradizionali. La maggior parte di essi, si basano su algoritmi adattativi, che permettono un'elevata precisione con un costo computazionale ragionevole. Nelle pagine che seguiranno si è cercato di capire le differenze e i vantaggi nell'utilizzare l'elaborazione di codice parallela (multi-threads) confrontando i tempi di esecuzione (al variare delle dimensioni dei dati di input, della strategia di distribuzione dei dati e il numero di threads interessati nell'esecuzione), effettuando una stima degli errori sui dati ottenuti. La tesi si snoda nei seguenti capitoli: ✔ Capitolo 1: Propone una panoramica generale, sulla concetto di Parallelizzazione, nonché una descrizione esclusivamente teorica, delle architetture e delle organizzazioni di memoria e delle differenti strategie di Luigi Perillo Z62/000007 Pagina 5 di 45 parallelizzazione disponibili attualmente. Si riportano inoltre esempi di architetture parallele e gli indici di classificazione per gli algoritmi paralleli. ✔ Capitolo 2: Descrive nel dettaglio il problema affrontato, e la metodologia adoperata per la risoluzione. Descrive la classe di algoritmo, le strategie utilizzate e l'harware predisposto per l'esecuzione e la verifica. Si passa ad una visione dettagliata delle solo architetture. Viene posta una differenziazione sulle funzioni sviluppate, specificando nel dettaglio, la strategia di parallelizzazione, la modalità di esecuzione ed i risultati attesi e poi ottenuti durante la fase di Testing del codice. Luigi Perillo Z62/000007 Pagina 6 di 45 1 Parallelismo Nella definizione più semplice di parallelismo, si intende, l'uso simultaneo di molte risorse di calcolo per l'esecuzione di un programma. Nell'ambito dell'elaborazione di grandi quantità di dati su terminali, risulta molto utile la suddivisione del carico di lavoro; poiché l'approccio con metodi statici tradizionali risulta dispendioso sia in termini di gestione di memoria che in tempi elaborazione. Nell'ultimo periodo si sta offrendo la possibilità di implementare soluzioni alternative per l'elaborazione sequenziale dei dati, sfruttando le caratteristiche del calcolo parallelo. La strategia si basa sulle evoluzione hardware, in pratica sviluppando un algoritmo parallelo che suddivide il carico di lavoro su più processori, portando un vantaggio significativo nel caso di lavoro su grandi moli di dati. Non si ottiene un calcolo parallelo efficiente semplicemente aumentando il numero di processori, eseguendo una connessione ad alta velocità. Nella pratica, è molto difficile ottenere uno speedup lineare, cioè prestazioni direttamente proporzionali al numero di processori presenti nel sistema, poiché molti algoritmi sono di natura sequenziale. Nella maggior parte degli algoritmi quindi occorre una riscrittura per poter sfruttare l'hardware parallelo. Un programma che viene svolto correttamente da una singola CPU potrebbe presentare problemi se svolto in parallelo, poiché più copie dello stesso blocco di istruzioni, potrebbero interferire tra loro, accedendo ad esempio ad una stessa area di memoria contemporaneamente. Da qui nasce quindi la necessità di una attenta programmazione per sfruttare questo tipo di sistemi. Luigi Perillo Z62/000007 Pagina 7 di 45 1.1 Architettura di Memoria I sistemi di calcolatori a seconda della molteplicità del flusso di istruzioni e del flusso dei dati che possono gestire, possono essere classificati secondo uno standard ben definito. La classificazione più utilizzata è la Tassonomia di Flynn, introdotta nel 1966. Questa classificazione, cataloga i computer in paralleli e seriali, nel caso in cui i processori eseguano la stessa istruzione contemporaneamente (SIMD, Single Instruction Multiple data) o istruzioni diverse (MIMD, multiple Instructions Multiple data). I sistemi paralleli sono anche classificati come simmetrici o asimmetrici, secondo le abilità possedute o ai compiti assegnati alle CPU (capacità di far girare tutto il codice del sistema operativo o solo una parte; accesso alla memoria e ai dispositivi di I/O). Di conseguenza ogni sistema di calcolo attuale, rientra in una delle seguenti categorie: ➢ SISD (Single Instruction Single Data) ➢ SIMD (Single Instruction Multiple Data) Processori vettoriali Array processor Array sistolici ➢ MISD (Multiple Instruction Single Data) ➢ MIMD (Multiple Instruction Multiple Data) Sistemi a memoria distribuita MPP Massively Parallel Processing COW Cluster Of Workstations Luigi Perillo Z62/000007 Pagina 8 di 45 Sistemi a memoria condivisa UMA Uniform Memory Access NUMA NonUniform Memory Access • NC-NUMA No Cache – NUMA • CC-NUMA Cache Coherent – NUMA NORMA NO Remote Memory Access COMA Cache only memory Access Macchine dataflow Macchine a riduzione Come è possibile notare dallo schema precedente, le quattro categorie principali (SISD,SIMD,MISD,MIMD), sono strutturare in sotto categorie, infatti categorie differenti saranno propense ad utilizzare organizzazioni di memoria di tipo diverso. Illustrazione 1: Schema Tassonomia di Flynn Luigi Perillo Z62/000007 Pagina 9 di 45 ➢ Architettura SISD L'architettura SISD è rappresentata dalla struttura della macchina di Von Neumann. Questo computer comprende una sola unità di elaborazione (ALU), che lavora su un unico flusso di istruzioni e opera su un solo flusso dati. Ad ogni ciclo di Clock, le istruzioni per l'elaborazione delle istruzioni vengono eseguite in sequenza. Durante la fase di Fetch, viene prelevata dalla memoria centrale, l'istruzione per elaborazione del comando da eseguire. Segue la fase di Decode, in cui viene interpretata l'istruzione da eseguire, durante la fase di Execute, in cui viene eseguita l'istruzione precedentemente stabilita. Illustrazione 2: Schema architettura SISD Possiamo semplificare, tale architettura dicendo, che è composta da: unità di controllo, unità di elaborazione e memoria. Luigi Perillo Z62/000007 Pagina 10 di 45 Un esempio di quest'architettura sono i processori tradizionali single-core, utilizzata in quasi tutti i personal computer, anche se ora risulta obsoleta. ➢ Architettura SIMD Questa architettura, rappresenta un multi-processore; cioè una struttura composta da un numero definito di processori identici. Tutti i processori, in modalità sincrona, eseguono la stessa istruzione su dati diversi. In questa architettura l'unità di controllo delle CPU organizza e distribuisce le istruzioni da inviare alle differenti unità di elaborazione logico aritmetica. L'architettura SIMD è spesso usata dai super-computer con alcune varianti anche nei moderni microprocessori. Gli algoritmi da parallelizzare su questo tipo di architettura, devono essere suddivisi in sotto problemi identici, poiché le istruzioni da eseguire sono le stesse per ogni processore. Uno dei vantaggio delle architetture SIMD è la capacità di manipolare tutti i dati caricati contemporaneamente; quindi se per esempio un processore SIMD risulta in grado di caricare n dati differenti, quest'ultimo sarà anche in grado di processarli contemporaneamente, ottenendo dei risultati buoni in termini di efficienza. Luigi Perillo Z62/000007 Pagina 11 di 45 Illustrazione 3: Schema architettura SIMD Questo tipo di architettura trova, applicazione nella realizzazione delle GPU, dove diversi stream, lavorano tutte sottoposte ad uno stesso controllo. ➢ Architettura MISD Risulta l'architettura meno utilizzabile, infatti prevede che un singolo flusso di dati, alimenti più unità di elaborazione contemporaneamente. Ogni singola unità lavora su dati indipendenti con flusso di istruzione differenti. Questa architettura esiste solo a livello sperimentale, ma non è mai stata commercializzata. Ad ogni ciclo di Clock, il dato ricevuto nella memoria viene elaborato da processori simultaneamente, utilizzando le istruzioni contenute nella propria unità di controllo. La differenziazione dei risultati è dovuta alle istruzione differenti eseguite sui dati. Luigi Perillo Z62/000007 Pagina 12 di 45 ➢ Architettura MIMD Attualmente risulta l'architettura più diffusa per il calcolo parallelo, poiché è molto potente durante la fase di calcolo. Questa architettura parallela è costituita da diverse unità che effettuano elaborazioni su diversi dati. Nell'architettura MIMD multiprocessore, questi condividono le strutture di dati in una memoria comune ai processori. Lo scambio delle informazioni avviene mediante la condivisione della memoria. Per questo motivo i processori necessitano di alcuni meccanismi di controllo che risolvano l'accesso simultaneo in fase di scrittura in memoria da parte di due o più processori contemporaneamente. In questa architettura il calcolo è molto veloce in Luigi Perillo Z62/000007 Pagina 13 di 45 quanto tutti i processori accedono simultaneamente alla memoria condivisa, però c'è da dire che tale architettura è anche molto complessa in quanto devono essere presenti numerosi circuiti di decodifica tra i processori. Nell'architettura MIMD, multi computer, ogni processore ha una propria memoria. I vari processori comunicano tra loro mediante uno scambio di messaggi definito message passing. La comunicazione tra i vari processori è affidata a una rete di interconnessione che consente a ciascun processore di trasmettere dati a qualunque altro presente nella stessa rete. I processori possono anche essere fisicamente lontani, come capita nei sistemi distribuiti. Illustrazione 5: Schema architettura MIMD Questa architettura è suddivisa in due differenti categorie, a seconda se viene utilizzata Memoria distribuita, ad esempio utilizzando dei Cluster, oppure a Memoria Condivisa, con la struttura a Multi-Core. Luigi Perillo Z62/000007 Pagina 14 di 45 1.2 Organizzazione della Memoria Uno degli aspetti più importanti della progettazione di un sistema di calcolo parallelo, riguarda il metodo di organizzazione della memoria. Questa risulta una parte fondamentale, per l'obbiettivo finale dell'aumento delle prestazioni, da cui non si può solo prescindere dalla velocità di accesso alla memoria per il recupero e l'elaborazione dei dati. Le architetture sono suddivise principalmente in tre categorie principali: Memoria condivisa, Memoria distribuita, ed una fusione tra le precedenti che prende il nome di Memoria ibrida Condivisa-Distribuita. ➢ Memoria Condivisa Nel modello a Memoria Condivisa, i processori interessati hanno accesso a tutta la memoria, che viene vista come un unico spazio di indirizzamento globale. I processori lavorano indipendentemente l'uno dall'altro, ma si ritrovano a condividere la memoria; ed in questo caso le modifiche saranno accessibili a tutti i processore collegati. A sua volta ogni processore risulta in possesso di una piccola cache locale, in cui memorizzare le informazioni, nella fase precedente alla modifica globale della memoria condivisa. Questa tecnica di indirizzamento globale, rende la programmazione parallela più intuitiva, nella realizzazione di un algoritmo per la parallelizzazione. Luigi Perillo Z62/000007 Pagina 15 di 45 Illustrazione 6: Architettura a memoria condivisa Questa architettura presenta anche notevoli svantaggi, tra cui la limitata scalabilità. Nel caso in cui si intendesse aumentare il numero di CPU, infatti ci sarebbe un aumento del traffico per le comunicazione tra le CPU e la memoria. ➢ Memoria Distribuita In un sistema con struttura a memoria distribuita, ogni processore risulta in possesso di una propria area di memoria locale, non condivisibile con gli altri processori del sistema. In questo caso i processori lavorano separatamente l'uno dall'altro e le modifiche alla memoria che avvengono in un determinato processore non influenzano la memoria locale degli altri. Lo scambio di informazioni tra i processori, è deciso dal programmatore, che Luigi Perillo Z62/000007 Pagina 16 di 45 gestisce i momenti e le modalità con cui deve avvenire lo scambio attraverso direttive di sistema. Questa tipologia di architettura di memoria è preferita per strutture di calcolo composte da più sistemi collegati tra loro attraverso una rete. Illustrazione 7: Architettura a memoria distribuita Questa architettura, consente una maggiore scalabilità del sistema, senza un aumento dei costi eccessivo. Infatti se in precedenza per la Memoria Condivisa, l'inserimento di nuovi processori, comportava modifiche del software che era stato implementato, in questo caso le modifiche risultano in prevalenza di tipo hardware, in quanto occorre ampliare esclusivamente la rete di collegamento dei processori. Le difficoltà come detto in precedenza si presentano nelle comunicazioni tra i processori, che portano anche ad un rallentamento generale, per la comunicazione dei pacchetti lungo la rete. Luigi Perillo Z62/000007 Pagina 17 di 45 ➢ Memoria Ibrida Distribuita-Condivisa I calcolatori moderni più grandi e veloci utilizzano oggi in prevalenza questo tipo di memoria. Questa architettura risulta una fusione delle precedenti; in pratica i differenti sistemi di calcolo, lavorano indipendentemente con la propria memoria condivisa e la connessione di rete viene utilizzata quando occorre muovere i dati da un sistema all'altro. Così come per le architetture a memoria condivisa, i sistemi in questo caso sono cache-coerency, mentre la componente distribuita è posta sulla rete per le connessione delle diverse componenti. La cache-coerency in questi sistemi è un problema molto complesso da gestire, che prevede l'utilizzo di interfacce di rete molto sofisticate. In questo caso, i vantaggi dall'utilizzo di questa architettura così come gli svantaggi sono una stretta fusione di quelli incontrati per le precedenti architetture. In prevalenza questa conformazione permette un'alta efficienza con macchine multiprocessori, con possibilità di scalare unità elevate di processori. Luigi Perillo Z62/000007 Pagina 18 di 45 Luigi Perillo Z62/000007 Pagina 19 di 45 1.3 Modelli di programmazione parallela Gli algoritmi strutturati per sfruttare il parallelismo, si basano su una serie di operazione che necessariamente devono essere svolte, al fine che un'operazione possa compiere correttamente il lavoro, producendo risultati corretti e finali. I modelli di programmazione parallela, sono sviluppati per essere multi-piattaforma, e quindi essere implementati per qualsiasi tipo di macchina. I modelli principalmente utilizzati per la programmazione parallela sono: Shared Memory, Messsage Passing. Non esiste un modello migliore in termini assoluti rispetto agli altri, la soluzione migliore da applicare, dipende molto dal problema che si deve risolvere o dal tipo di hardware a disposizione. ➢ Shared Memory In questo modello i task condividono un'unica area di memoria, nella quale vengono scritti in modalità asincrona le informazioni. In alcuni sistemi può accadere che i processi in esecuzione condividano una risorsa comune. Se si verifica che il risultato finale dell'esecuzione di più processi dipende dall'ordine in cui essi vengono eseguite queste operazioni, ci troviamo in presenza di una corsa critica (race-condition). Il problema delle "corse critiche" può essere evitato impedendo che più di un processo per volta acceda a risorse condivise. Uno dei metodi per ovviare ciò, è la mutua esclusione; in questo caso i sistemi concorrenti mettono a disposizione delle primitive di comunicazione che permettono di alternarsi (escludendosi a vicenda) nell'accesso ad una risorsa condivisa oppure usando delle primitive di sincronizzazione che permettono di intervenire sulla sequenza secondo la quale avverranno determinati eventi. Ogni sistema mette a disposizione le proprie primitive, ma nonostante ciò i sistemi Luigi Perillo Z62/000007 Pagina 20 di 45 si rifanno ad una teoria comune. Le più comuni vie di sincronizzazione e comunicazione tra processi sono: • Mutex: variabili binarie che permettono di gestire l'accesso ad una risorsa condivisa mediante accesso esclusivo di uno dei contendenti. Le operazioni di blocco e di sblocco sono operazioni atomiche (eseguite in sequenza, senza interruzione). • Semafori n-ari: variabili n-arie che possono essere incrementate e decrementate. Il processo che decrementa il semaforo si bloccherà appena raggiunto lo zero della variabile. Un processo che incrementa il semaforo invece risveglia tutti i processi che si erano bloccati in fase di decremento. Questa è una delle primitive base che consentono la sincronizzazione. Le operazioni di incremento, decremento e controllo del semaforo sono anch'esse atomiche. • Variabili di tipo condizione: conosciute anche come Condition Variables, sono variabili con associate due primitive: wait e signal. Una procedura che richiama la primitiva wait si pone in attesa indefinita, una procedura che richiama la primitiva signal ne risveglia una in attesa su wait. Anche in questo caso le operazioni sono atomiche. • Scambio di messaggi: due procedure, send (invia) e receive (ricevi), permettono a due processi di scambiarsi messaggi. Lo scambio di messaggi è solitamente usato in sistemi paralleli. • Barriere: un processo che ha terminato la sua fase chiama una primitiva barrier e si blocca. Quando tutti i processi coinvolti hanno terminato il loro stadio di esecuzione invocando anch'essi la primitiva barrier, il sistema li sblocca tutti permettendo di passare ad uno stadio successivo. Risulta molto semplice l'implementazione di questo tipo di programmazione, anche Luigi Perillo Z62/000007 Pagina 21 di 45 nel caso in cui si passa da una versione seriale dell'algoritmo di interesse. In questa categoria subentra la gestione multithreading, infatti il processo da eseguire può avere percorsi multipli, alternando esecuzioni sequenziali a blocchi di codice parallelo. Alcune implementazioni principali di questo modello sono: 1. OpenMP 2. Posix Threads (Pthreads). Questo è il modello utilizzato per l'algoritmo sviluppato, in quanto permette di raggiungere gli obbiettivi fissati, sfruttando al meglio la struttura di calcolo a disposizione. ➢ Message Passing Il modello Message-Passing viene solitamente applicato in sistemi in cui l'architettura di memoria è distribuita. Più task possono essere situati sulla stessa macchina, oppure, su un numero di macchine random. In questo caso il programmatore, è portato a determinare il parallelismo e lo scambio dei dati, che avviene attraverso messaggi, richiamando una libreria da inserire all'interno del codice. Uno modello standard è MPI (Message Passing Interface), implementato per architettura a memoria distribuita ma adattabili anche a macchine multi-piattaforma a memoria condivisa. Luigi Perillo Z62/000007 Pagina 22 di 45 1.4 Valutare algoritmi paralleli Lo sviluppo della tecnologia di Calcolo Parallelo, ha portato alla necessità di avere strumenti di valutazione per poter constatare le prestazioni, e gli effettivi vantaggi nella parallelizzazione di algoritmi. Come detto in precedenza, la parallelizzazione di algoritmi in precedenza eseguiti sequenzialmente, ha come scopo prevalente quello di risolvere problemi di grandi dimensioni in tempi relativamente brevi. I fattori da avere sotto controllo per il raggiungimento di questo obbiettivo, sono molteplici e vanno, come detto, dalla tecnologia hardware utilizzata, al modello di parallelizzazione (ampiamente spiegati in precedenza) scelto, a seconda delle caratteristiche strutturali su cui si basa l'algoritmo. Per l'analisi delle prestazioni fornite dagli algoritmi paralleli, sono stati introdotti dei concetti, che confrontano in prima istanza le prestazioni con la versione originale dell'algoritmo, in modo da verificarne l'effettivo aumento di prestazioni, o modificandone le caratteristiche, variandone il numero di processori e/o thread per le singole esecuzioni. Gli indici che permettono di analizzare questo valori sono: Speed Up, Efficienza e Scalabilità. ➢ Speed Up Uno degli strumenti per confrontare un algoritmo sequenziale con il suo corrispettivo parallelo, è chiamato Speed Up. Lo Speed Up è definito come: S p= T1 Tp dove in dettaglio, “p” è il numero di processori, “T 1” è il tempo di esecuzione nella Luigi Perillo Z62/000007 Pagina 23 di 45 versione sequenziale dell'algoritmo, “Tp” è il tempo di esecuzione della versione parallela dell'algoritmo con “p” processori. Nel caso in cui il nostro algoritmo risulti avere uno Speed Up Ideale (Sp=p), ci troveremo nella condizione in cui, raddoppiando il numero di processori si verificherà un raddoppio della velocità di esecuzione. In presenza di questa caratteristica, l'algoritmo avrebbe la maggiore scalabilità, cioè la massima versatilità al variare delle necessità o della disponibilità del problema. ➢ Efficienza Nell'ambito delle analisi delle prestazioni, l'efficienza viene definita nel seguente modo: E p= Sp T1 = p ( p∗T p ) Questo parametro compreso tra i valori zero ed uno, permette di effettuare una stima di quanto i processori vengano sfruttati per la risoluzione del problema, rispetto alle altre attività assegnate all'algoritmo, come la comunicazione tra i processori e la sincronizzazione. Sulla base di quanto appena detto definiamo con T(k,z) il tempo di elapsed (tempo di esecuzione) totale per completare l'esecuzione, avendo dimensione del problema z con k thread. T ( k , z)=T s + T c (z ) +T 0 (k ) K Avremo quindi: • Ts, tempo di esecuzione per le sezioni seriali dell'algoritmo. Ts risulta indipendente dalla dimensione del problema che indicheremo con z, e dal numero di threads k interessati nell'elaborazione. Luigi Perillo Z62/000007 Pagina 24 di 45 • T c ( z) K indica il tempo di esecuzione per le sezione parallelizzabile dell'algoritmo. Indica che la sezione di codice da parallelizzare può essere decomposta ed eseguita in contemporanea k volte dai thread, avendo dei tempi di esecuzioni uguali. • T0(k), tempo di comunicazione tra i thread, utilizzato per scambio di informazioni e dei risultati parziali di elaborazione. Questa è una funzione non decrescente, in funzione del numero di thread, per cui avremo che per T0(1)=0, cioè nell'esecuzione ad un solo thread, l'Overhead di comunicazione sarà 0, poiché non esistono comunicazione tra i vari thread. L'efficienza R(N,z), sarà data da: R( N , z)⩽ T (1, z ) ⩽1 T ( 1, z )+T 0( N ) I differenti tempi di overhead che si possono riscontrare, per la sincronizzazione dei risultati e per la comunicazione tra i thread, influiscono notevolmente sul calcolo dell'efficienza, maggiore sarà l'Overhead, più le prestazioni dell'algoritmo saranno meno accettabili. Prendendo come esempio l'algoritmo che esegue una somma a cascata, a seconda della strategia e della gestione di memoria utilizzata, possiamo trovarci in presenza di fattori di overhead, che influiscono sull'efficienza dell'algoritmo. Nel caso di una struttura MIMD, a memoria distribuita, il tempo di overhead risulta pari a: T 0 ( p)=log 2 ( p) . Utilizzando invece un'architettura a memoria condivisa, con attraversamento di una regione critica, ci troveremo ad avere un overhead, dipendente dal numero di Luigi Perillo Z62/000007 Pagina 25 di 45 processori interessati nell'elaborazione dei risultati, quindi pari a: T 0 ( p)= p , un fattore molto influente in questo caso, se il numero di processori interessati nell'elaborazione risulta elevato. Nel caso in cui ci si trova in una completa assenza di comunicazione tra i processori, ci troveremo in presenza di assenza di overhead, quindi per algoritmi denominati Embarrassingly parallel avremo che: T 0 ( p)=1 . Classifichiamo quindi l'Efficienza nel seguente modo: Ep = 1 Caso Ideale Ep < 1 Caso Reale. ➢ Scalabilità La Scalabilità viene definita come la capacità di un algoritmo di essere efficiente su una macchina parallela. Identifica in breve, la possibilità di incremento della potenza di calcolo, proporzionalmente all'aumento del numero di processori. Quindi in presenza dell'aumento della dimensione del problema, e allo stesso tempo effettuando un aumento del numero di processori nell'esecuzione, le prestazioni restano stabili senza perdita di efficienza. Questo risulta essere l'obbiettivo principale, per cui si progettano algoritmi paralleli. Luigi Perillo Z62/000007 Pagina 26 di 45 2 Problema affrontato L'algoritmo che si è analizzato, come accennato in precedenza, esegue il calcolo della quadratura numerica per la risoluzione di un integrale definito in un dominio [0,1]10 su una funzione a dieci variabili, utilizzando algoritmi adattativi [1] per la quadratura in dieci dimensioni [2]. U =[0,1] d with d =10 f (x )=0 if x1> β 1 ∨ x 2> β 2 ; f (x)= exp( ∑ i=1,..., d ai x i ) Tale algoritmo dimezza successivamente l'ampiezza dei sotto intervalli, affinché una stima dell'errore commesso, non risulta inferiore ad una fascia di tolleranza assegnata tra i parametri di input. In se questo risulta essere un problema semplice, nel caso in cui la funzione da determinare è costituita da poche unità di variabili, mentre tende ad aumentare il carico di lavoro, in presenza di un range di valori più ampio per le variabili della funzione. Nel caso in cui il comportamento della funzione non è lo stesso nell'intervallo di integrazione, si cerca di adattare la distribuzione dei nodi alle proprietà della funzione integrata. Per la risoluzione si è usufruito del calcolo parallelo, su un architettura MIMD, con memoria condivisa, utilizzando una strategia Multi-Threads, sfruttando la libreria Pthread. L'implementazione di questa strategia, consente nell'ambito della programmazione parallela, di guadagnare tempo in modo molto rilevante, poiché lo switching tra thread è molto più veloce che non quello tra processi. Il problema generale da affrontato, è quello della suddivisione della parte parallelizzabile dell'algoritmo, tra i diversi threads, in modo da avere un carico di Luigi Perillo Z62/000007 Pagina 27 di 45 lavoro bilanciato tra i diversi threads, senza sovraccaricare di lavoro solo alcuni di essi e rendere inattivi gli altri. La strategia utilizzata, prevede la suddivisione del dominio in N thread parti, per poi sfruttare la proprietà additiva degli integrali. L'algoritmo adattativo, permette di raffinare gli intervalli dove l'errore di elaborazione è più grande, dividendolo in aree più piccole, da distribuire ai thread in cui la stima dell'errore risulta minima, cioè ai threads che si trovano ad esaminare parti della funzione in cui l'andamento è di facile calcolo (lineare). Le metodologie di ridistribuzione che si sono prese in esame sono in prevalenza due: • Comunicazione globale. Ogni nodo ha dei sotto domini propri. Il nodo identifica l'area con l'errore maggiore e comunica a tutti gli altri nodi le informazioni per la ripetizione dell'esecuzione e per la riduzione dell'errore. I punti negativi rilevanti in questa strategia, sono le eccessive comunicazioni tra i thread, che comporta un elevato overhead , ma di conseguenza comporta un accuratezza molto elevata dei risultati calcolati, T 0 ( p)= p • . Comunicazioni locali. Ogni nodo comunica solo con i nodi adiacenti. Questo comporta una riduzione drastica dell'overhead, in quanto indipendentemente dal numero di nodi interessati nell'elaborazione, le comunicazioni restano di numero costante, T 0 ( p)=cost . Questa strategia comporta quindi una notevole scalabilità, che la rende preferibile rispetto ad un algoritmo in cui le strategie di comunicazioni sono globali. La strategia scelta è la seconda, assegnando ad ogni nodo in esecuzione un algoritmo iterativo, che in ogni passo di iterazione effettua una comunicazione in Luigi Perillo Z62/000007 Pagina 28 di 45 senso orizzontale o verticale, riducendo ulteriormente il numero di comunicazioni locali a 2 tra i 4 nodi adiacenti. Luigi Perillo Z62/000007 Pagina 29 di 45 2.1 Descrizione Algoritmo L'algoritmo consente la distribuzione dei dati di input sui diversi thread, utilizzando due strategie di distribuzione differenti, in modo da ottimizzare oltre al tempo effettivo di esecuzione della funzione sviluppata, anche la stima dell'errore per determinare la risoluzione dell'integrale. A seconda di come sono distribuiti i dati sui vari thread, sarà possibile ottenere una stima dell'errore minore all'aumentare del numero di thread, e quindi una maggiore accuratezza dei risultati ottenuti. L'integrale da risolvere viene suddiviso in sub-regioni da assegnare ai vari thread ed a seconda della suddivisione, sarà stimato l'errore. L'errore stimato viene determinato in un confronto con il risultato ottenuto dalla funzione che determina l'integrale noto, utilizzando il calcolo della primitiva nota della funzione. La suddivisione in sub-sezioni dipende dal numero di thread e dal numero di valutazione di funzione. Ai diversi thread (1, 2 o 4) impiegati nell'esecuzione, sono assegnate differenti sezioni della funzione, nel caso in cui un thread si trova con un carico di lavoro minore, dovuto ai risultati ottenuti che non appartengono al range di valori accettabili, avviene un'ulteriore suddivisione, che consente di bilanciare il carico dei dati e di ottimizzare esecuzione e l'errore di elaborazione. Il progetto è stato compilato, in ambiente Linux, da riga di comando con la seguente sintassi: cc auxiliary.c c_timer.c environ.c pamihr.c main.c -o eseguibile -lm -lpthread dove: • pamihr.c, la funzione sviluppata per la risoluzione dell'integrale. • main.c, funzione in cui sono contenute le chiamate alle varie procedure e Luigi Perillo Z62/000007 Pagina 30 di 45 dove sono gestite le strutture per il salvataggio delle informazioni di output. • -lm, modulo per la gestione della libreria matematica. • -lpthread, modulo per la gestione della libreria Pthread. 2.2 Strategia di distribuzione dei dati Le strategie adottate per la risoluzione del problema, sono sostanzialmente due. Occorre quindi valutare in termini di efficienza e accuratezza quale approccio, risulta il migliore da seguire, in quanto ognuna delle due strategie come sarà possibile osservare dai test effettuati, ha delle caratteristiche che ne consente di favorirne una rispetto l'altra, puntando ad avere una maggiore accuratezza dei risultati ottenuti, o ad avere tempi di esecuzione inferiori. La differenza sostanziale tra le due strategie si presenta nel modo in cui i dati (precaricati, e dati in input alla funzione PAMIHR) [3] vengono distribuiti ai thread e di conseguenza se devono essere create e gestite le sub-sezioni, per la risoluzione e di conseguenza le comunicazioni tra i vari thread impegnati nell'esecuzione. Le due differenti strategie sono settate con l'ausilio di una variabile semaforo chiamata REDIST, che può essere settata con valore rispettivamente 1 o 0. • Strategia di distribuzione: Redist 0 Questa strategia denominata Redist 0, non prevede la possibilità di ridistribuire i dati e quindi di creare sotto sezioni, da assegnare ai thread, nel caso in cui in una particolare area, sia presente un errore d'elaborazione elevato. Questa strategia, non è affidabile nel caso in cui si vuole puntare ad una maggiore accuratezza dei risultati, poiché l'errore di elaborazione risulta maggiore; in compenso, risulta essere una strategia utilizzabile nel caso in Luigi Perillo Z62/000007 Pagina 31 di 45 cui si punta ad ottenere una maggiore efficienza in termini di tempi di elaborazione. • Strategia di distribuzione: Redist 1 La strategia denominata Redist 1, permette di avere una maggiore accuratezza dei risultati ottenuti, avendo una riduzione della errore stimato, tra l'algoritmo parallelo, e la risoluzione con funzioni già note. In questa strategia, i tempi di esecuzione, sono soggetti ad overhead di comunicazione costante, tra i nodi adiacenti secondo la strategia spiegata in precedenza nel paragrafo 2. In presenza di aree in cui i thread riscontrano un errore elevato, avviene una ridistribuzione di solo una parte di questa area, tra i thread adiacenti, che consentiranno di ridurre l'errore di calcolo e di avere quindi risultati più attendibili e prossimi a quello calcolati da funzioni note. Le differenze tra le due strategie, nel caso in cui si prede in considerazione l'efficienza dell'elaborazione, risulta essere di poco meno del 10%. Una maggiore differenza, la si ottiene nel caso in cui si punta all'accuratezza dei dati, in questo caso tra gli errori stimati sui risultati, sarà possibile riscontrare un fattore ben più evidente, dimostrando l'efficacia della strategia di ridistribuzione adottata. Ciò è dovuto al fatto che le comunicazioni tra i thread sono ridotte, (ad ogni passo iterativo, sono al massimo 2 per ogni nodo, e non N-1, come nel caso della strategia globale descritta in precedenza). Questa caratteristica sarà ben più visibile nel paragrafo successivo, in cui sono riportati i test effettuati sia per efficienza e sia per stima dell'errore. Luigi Perillo Z62/000007 Pagina 32 di 45 2.3 Test performance e stima errori Nel capitolo precedente è stata fatta una panoramica sull'algoritmo. In particolare è stata mostrata la metodologia adottata per la risoluzione, mostrando le due differenti strategie per ridistribuire i dati sui thread predisposti per l'elaborazione. Si passa ora ad analizzare da un punto di vista più teorico le performance dell'algoritmo, per la valutazione dei punti di forza della parallelizzazione sui differenti casi riscontrati, per condizioni di input da noi definite. Un primo test effettuato, si è basato sull'analisi dei tempi di esecuzione della funzione Pamihr, per il calcolo dell'integrale posto in input, facendo variare, la dimensione del problema ed il numero di thread. Svolto questo test, si è passato all'analisi dei tempi, determinando in ogni caso, lo Speed Up e l'Efficienza, così da poter valutare l'utilità della parallelizzazione del codice e la Scalabilità della struttura progettata. Il secondo test effettuato, si orienta sui risultati dell'elaborazione ottenuti. Sono riportati i valori ottenuti dalla funzione sviluppata, in confronto ai risultati del calcolo dell'integrale mediante primitive già note. Di questi test, si è svolta una stima degli errori, verificando l'accuratezza dei dati, nelle due diverse strategie implementate. I tempi e le stime degli errori riportati nei paragrafi a seguito, sono il risultato della media di tre differenti esecuzioni, in modo da ammortizzare le fluttuazioni a runtime. I test sono stati svolti su di una macchina in dotazione del dipartimento di matematica, che riporta le seguenti caratteristiche. • Processor 8-core Intel(R) Core(TM) i7 CPU, 950 @3.07GHz con 8192 KB di cache-size. Luigi Perillo Z62/000007 Pagina 33 di 45 2.3.1 Test efficienza algoritmo Come detto in precedenza, il primo test effettuato è l'analisi delle performance della funzione Pamihr, che determina il calcolo dell'integrale definito di una funzione a dieci variabili. Le prove sono effettuate sulle variazione di thread 1, 2 o 4 e su dimensioni di input che vanno da 250.000 a 1.000.000 e al variare del tipo di ridistribuzione dei dati, settando la variabile Redist. Dimensioni Threads Valori 250000 500000 1 Elapsed Medio 0,013508 0,025782 2 Elapsed Medio 0,007446 0,012800 4 Elapsed Medio 0,005109 0,007798 750000 0,039444 0,020221 0,011841 1000000 0,051660 0,027679 0,015354 Tabella 1: Tempi di esecuzione. Redist 0 Threads 1 2 4 Valori Elapsed Medio Elapsed Medio Elapsed Medio Dimensioni 250000 0,014241 0,008482 0,005464 500000 0,025167 0,015012 0,009147 750000 0,038562 0,022616 0,012468 1000000 0,050412 0,029972 0,016506 Tabella 2: Tempi di esecuzione. Redist 1 Come si può notare dai tempi riportati nelle tabelle precedenti, all'aumentare del numero di thread, impiegati nell'esecuzione, i tempi tendono a diminuire, andando in alcuni casi, a dimezzarsi di circa il 50% rispetto al tempo di esecuzione nel caso strettamente precedente di numero di thread. È possibile riscontrare gli effettivi vantaggi della parallelizzazione nelle tabelle successive, che riportano lo Speed Up e l'Efficienza media. Luigi Perillo Z62/000007 Pagina 34 di 45 Threads 1 2 4 Valori Speed Up Speed Up Speed Up Dimensioni 250000 1 1,824952 2,817228 500000 1 2,016069 3,416137 750000 1 1,952758 3,416137 1000000 1 1,882525 3,519785 Tabella 3: Speed Up medio su 3 esecuzioni. Redist 0 3,5 3 Speed Up 2,5 2 250000 500000 750000 1000000 1,5 1 0,5 0 1 2 4 Threads Illustrazione 10: Grafico Speed Up, Redist 0 Luigi Perillo Z62/000007 Pagina 35 di 45 Threads 1 2 4 Valori Speed Up Speed Up Speed Up Dimensioni 250000 500000 1 1 1,684318 1,673195 2,722445 2,800481 750000 1 1,702252 3,088452 1000000 1 1,679207 3,049022 Tabella 4: Speed Up medio su 3 esecuzioni. Redist 1 3,5 3 Speed Up 2,5 2 250000 500000 750000 1000000 1,5 1 0,5 0 1 2 4 Threads Illustrazione 11: Grafico Speed Up, Redist 1 Luigi Perillo Z62/000007 Pagina 36 di 45 Dai calcoli effettuati per lo Speed Up su entrambe le strategie, si nota che le prestazioni dell'algoritmo non risultano perfettamente conformi alla definizione di Speed Up, questo dipende da alcuni fattori esterni alla parallelizzazione su thread, come ad esempio i tempi di overhead di trasmissione dei dati e al tipo di strategia utilizzata per la distribuzione. Le prestazioni dell'algoritmo restano comunque molto apprezzabili, di fatto all'aumentare delle dimensioni del problema ed all'aumentare del numero dei thread impiegati per l'elaborazione, l'algoritmo risulta molto performante. In particolare la strategia di ridistribuzione Redist 0, risulta molto più performante rispetto all'altra strategia e quindi preferibile nel caso in cui si voglia puntare come caratteristica prevalente, ad avere tempi di esecuzione migliori e quindi risposte in tempi minori. Le tabelle seguenti riportano per completezza l'Efficienza media, per entrambe le strategie di distribuzione. Threads 1 2 4 Valori Efficienza Efficienza Efficienza Dimensioni 250000 500000 1 1 0,912476 1,008034 0,704307 0,854034 750000 1 0,976379 0,862745 1000000 1 0,941262 0,879946 Tabella 5: Efficienza media su 3 esecuzioni. Redist 0 Luigi Perillo Z62/000007 Pagina 37 di 45 1,2 1 0,8 Efficienza 250000 0,6 500000 750000 0,4 1000000 0,2 0 1 2 4 Threads Illustrazione 12: Grafico Efficienza Media, Redist 0 Threads 1 2 4 Valori Efficienza Efficienza Efficienza Dimensioni 250000 500000 1 1 0,842159 0,836598 0,680611 0,700120 750000 1 0,851126 0,772113 1000000 1 0,839604 0,762256 Tabella 6: Efficienza media su 3 esecuzioni. Redist 1 Luigi Perillo Z62/000007 Pagina 38 di 45 1 0,8 0,6 Efficienza 250000 500000 0,4 750000 1000000 0,2 0 1 2 4 Threads Illustrazione 13: Grafico Efficienza Media, Redist 1 Come detto in precedenza, è possibile notare, dalle tabelle e dai grafici precedenti come le prestazioni della strategia di distribuzione Redist 0, risultano preferibili, rispetto all'altra strategia e dalle tabelle precedenti sull'Efficienza media, risulta più evidente, in quanto l'efficienza tende ad avvicinarsi all'Efficienza Ideale, descritta nel paragrafo 1.4. Luigi Perillo Z62/000007 Pagina 39 di 45 2.3.2 Test stima errore Il secondo test sviluppato sull'algoritmo, è quello che analizza i risultati delle elaborazioni, confrontando i risultati ottenuti con i valori, riscontrati dal calcolo dell'integrale con le funzioni note. Di questi valori, è stato effettuata una stima dell'errore, per verificare gli effettivi vantaggi, e la correttezza delle varie elaborazioni. Le prove sono effettuate sulle variazione di thread 1, 2 o 4 e su dimensioni di input che vanno da 250.000 a 1.000.000 e al variare del tipo di ridistribuzione dei dati, settando la variabile Redist. I grafici che seguiranno sono su scala logaritmica, per rendere visibile gli effettivi vantaggi e le differenziazioni tra le due strategie di calcolo. Thread 1 2 4 Valori Act. Error Est. Error Act. Error Est. Error Act. Error Est. Error Dimensioni 250000 0,071017 0,004370 0,071364 0,017965 0,071170 0,029775 500000 0,071170 0,003610 0,070998 0,002639 0,070994 0,006546 750000 0,071035 0,000047 0,071031 0,000775 0,070992 0,002733 1000000 0,071035 0,000011 0,071036 0,000240 0,071036 0,001037 Tabella 7: Errori Elaborazione. Redist 0 Luigi Perillo Z62/000007 Pagina 40 di 45 0,050000 0,029775 0,017965 0,006546 0,004370 0,005000 0,003610 0,000500 0,002639 0,002733 0,000775 0,001037 0,000240 Errori 250000 500000 0,000047 0,000050 750000 0,000011 0,000005 1 Threads 2 4 Illustrazione 14: Grafico Errore stimato. Redist 0 (scala logaritmica) Thread Valori 1 Act. Error Est. Error 2 Act. Error Est. Error 4 Act. Error Est. Error Dimensioni 250000 0,071017 0,004370 0,071035 0,004374 0,071022 0,014005 500000 0,071038 0,000361 0,071037 0,000371 0,071032 0,000485 750000 0,071035 0,000047 0,071035 0,000048 0,071035 0,000066 1000000 0,071035 0,000011 0,071035 0,000011 0,071035 0,000013 Tabella 8: Errori elaborazione. Redist 1 Luigi Perillo Z62/000007 Pagina 41 di 45 0,050000 0,014005 0,004374 0,005000 0,004370 0,000371 0,000500 0,000361 Errori 0,000048 0,000485 0,000066 0,000050 0,000047 0,000011 0,000013 2 4 0,000011 0,000005 1 250000 500000 750000 1000000 Threads Illustrazione 15: Grafico Errore stimato. Redist 1 (scala logaritmica) Come si può osservare dai valori riportati nelle tabelle e sui grafici, si ottengono notevoli vantaggi di accuratezza dati in presenza della strategia di ridistribuzione denominata Redist 1. I dati riportati evidenziano che le prestazioni ottenute dall'algoritmo all'aumentare della dimensione dell'input e del numero di thread, tendono ad avere un errore stimato dell'ordine 10 -5 rispetto al valore dello stesso integrale di partenza, determinato tramite funzioni di primitive note. Dai test effettuati, si evidenzia maggiormente quanto la scelta della strategia da applicare, dipenda dal tipo di riscontro che si vuole ottenere, cioè un vantaggio esclusivamente temporale, a discapito dell'accuratezza dei dati di output, o viceversa. Luigi Perillo Z62/000007 Pagina 42 di 45 3 Conclusioni Nell'ambito della analisi della risoluzione mediante quadratura di integrali definiti, in un dominio [0,1]10 di funzioni complesse a dieci variabili, si è seguita la strada della risoluzione mediante l'uso del calcolo parallelo, per ottenere vantaggi sia in termini di tempi di esecuzione, sia per avere delle approssimazioni migliori dei risultati di calcolo. Lo scopo era quello di approfondire una metodologia di valutazione di funzioni per il calcolo scientifico, in ambiente computazionali basati su CPU multicore. Come caso di studio si è deciso di utilizzare una classe speciale di algoritmi per la quadratura numerica; per poi applicare questa metodologia anche ad altri tipi di problemi, come la manipolazione delle matrici, utilizzando funzioni test per esaminare la strategia, oppure far eseguire più volte lo stesso problema e fare la media in maniera tale da ammortizzare le variazioni a run-time. La direzione adottata nella parallelizzazione dell'algoritmo è stata quella di utilizzare l'architettura MIMD, con gestione multi-thread con l'ausilio della libreria Pthread. Ciò ha portato dapprima a svolgere dei test, per la valutazione dell'ambiente di calcolo determinando l'effettivo vantaggio di questo approccio, valutando l'efficienza prestazionale; per poi passare ad un'analisi accurata dei risultati ottenuti, sulla loro corrispondenza ai valori determinati tramite lo studio della primitive note, per la funzione conosciuta data in input. Dalle analisi svolte, si è giunti alla conclusione che l'algoritmo sviluppato, pone il programmatore di fronte ad una scelta, a seconda del risultato che si è prefissato di ottenere. Le due strategie denominate rispettivamente Redist 0 e Redist 1, permettono di scegliere, se si desidera riscontrare un vantaggio in termini di tempi di esecuzione, ciò però a discapito dell'accuratezza dei dati, o se puntare su una precisione Luigi Perillo Z62/000007 Pagina 43 di 45 millesimale dei dati, con un errore stimato molto piccolo. Le motivazioni di tale considerazione, risiedono principalmente, dal numero di thread impiegati nell'elaborazione, e dalla possibilità di ridistribuire i dati di input sui diversi threads, quindi adoperando un algoritmo adattativo, piuttosto che calcoli separati, senza cooperazione tra i thread, che portano ad una minore qualità dei risultati, (anche se non eccessiva), in termini di tempi minori di esecuzione, dovuti soprattutto all'assenza dell'overhead di comunicazione e alla sincronizzazione delle aree di memoria condivise tra i thread. Luigi Perillo Z62/000007 Pagina 44 di 45 Bibliografia 1. Almerico Murli: Libro: “Matematica numerica: metodi, algoritmi e software Parte Prima” – Liguori Editore; (2007) ; pp 508. 2. A. Genz and A. Malik; Articolo:“An embedded family of fully symmetric numerical integration rules”; SIAM Journal on Numerical Analysis; 20 (1983); pp. 580-588. 3. Giuliano Laccetti, Marco Lapegna, Valeria Mele, Diego Romano: Articolo: “A study on adaptive algorithms for numerical quadrature on heterogeneous GPU and multicore based systems”. Luigi Perillo Z62/000007 Pagina 45 di 45