• Non ci sono risultati.

Differenze tra riconfigurazioni funzionali e non funzional

5.4 Adattivit` a in ASSISTANT

5.4.3 Differenze tra riconfigurazioni funzionali e non funzional

Cerchiamo ora di capire per quale motivo abbiamo deciso di considerare le riconfigurazioni non funzionali come “diverse” da quelle funzionali. Le motivazioni di questa scelta nascono, ancora una volta, sia per motivi di ottimizzazione, sia per motivi “storici”. Come abbiamo gi`a detto, la prima `e ereditata dal modello di ASSIST, nel quale `e stata trattata come unica tecnica di adattivit`a. Inoltre, non richiede l’interazione del programmatore, in quanto viene creata automaticamente dal supporto di ASSISTANT. No- nostante questo, niente vieterebbe di considerarla semplicemente come caso specifico di una pi`u vasta gamma di riconfigurazioni.

In realt`a il conservare la forma di parallelismo e il codice utente ci per- mette di inserire una ottimizzazione molto importante, che nell’altro caso, in generale, non `e possibile.

Ricordiamo che i moduli ASSISTANT hanno una semantica “a stream”, ovvero eseguono la stessa operazione ad ogni attivazione del modulo. Le ri- configurazioni, di qualsiasi tipo esse siano, devono mantenere una semantica corretta e ben precisa del modulo. A questo proposito, anche in applicazio- ni puramente sequenziali, `e necessario poter definire in modo preciso cosa succede ai dati nel caso avvenga una riconfigurazione. Nel caso parallelo la situazione `e ancora peggiore, in quanto in realt`a stiamo modificando il com- portamento di tanti programmi che cooperano insieme: garantire una forma di correttezza non `e necessariamente banale n´e da ottenere, n´e da dimostrare. Non possiamo “riconfigurare” un modulo in qualsiasi momento della sua esecuzione, in quanto questo porterebbe a risultati impredicibili ed inconsi- stenti. Al contrario, vogliamo garantire una semantica ben precisa nel ca- so di una riconfigurazione, che ci garantisca un risultato “valido”, ovvero producibile da uno dei due comportamenti in gioco.

Per fare questo, un modo semplice (ma spesso anche l’unico, se non si hanno informazioni aggiuntive) `e quello di assicurare che ogni attivazione del modulo viene trattata da un singolo comportamento. Questo vuol dire che la computazione in corso durante la riconfigurazione verr`a eseguita intera- mente da una singola versione del modulo. Non `e necessario che sia quella precedente alla riconfigurazione, in quanto si hanno due possibilit`a:

1. continuare l’elaborazione corrente con il vecchio comportamento, ed applicare il nuovo solo alle successive attivazioni del modulo;

2. annullare l’elaborazione corrente, e rieseguirla col nuovo comporta- mento.

Ovviamente entrambe le soluzioni hanno un senso, ed esistono casi un cui potrebbe essere meglio utilizzare la prima, altri in cui questa non `e pi`u praticabile, e siamo in qualche modo “costretti” ad utilizzare la seconda.

Tutto questo, per`o, nel caso “generale” di una qualsiasi riconfigurazione. Con le riconfigurazioni non funzionali il modello ad alto livello di ASSI- STANT ci permette di fare qualcosa in pi`u: ci offre una terza scelta che `e sempre migliore delle due precedentemente discusse.

Infatti, la modellazione ad alto livello ci permette di definire dei punti, all’interno della computazione, in cui lo stato dell’intero modulo `e “consisten- te”. In questi momenti (che saranno poi utilizzati anche per ottenere tolle- ranza ai fallimenti) lo stato del modulo `e ben definito e pu`o essere utilizzato come punto di partenza di una nuova computazione.

In una prima approssimazione, raggiungere uno stato consistente per l’in- tero modulo richiede dei momenti di “sincronizzazione”, dove le singole entit`a

del modulo si fermano, in attesa delle altre. Continuare la propria elaborazio- ne porterebbe a modifiche dello stato interno rendendolo non pi`u consistente. Questa limitazione pu`o essere facilmente superata tramite tecniche di chec- kpoint non coordinato, dove queste entit`a salvano, in momenti ben precisi, il proprio stato interno mediante tecniche di checkpointing. Gli studi in [20] dimostrano come sia possibile utilizzare tali tecniche per formare check- point “validi” dello stato dell’intera applicazione. In questo modo possiamo definire uno stato consistente come l’ultimo checkpoint globale raggiunto, ed eliminare la necessit`a di sincronizzazione. La definizione di checkpoint consistenti permette di implementare in modo ottimizzato sia tolleranza a guasti/fallimenti, sia i meccanismi di riconfigurazione.

Mantenere la stessa struttura parallela e lo stesso codice utente in ASSI- STANT significa (nel caso di topologia array) ridistribuire i Virtual Processor su un insieme differente di nodi. In questo caso ci basta ridistribuire lo stato sui nuovi nodi, ed il modulo riconfigurato potr`a continuare l’esecuzione dal punto raggiunto con il vecchio comportamento.

Per la topologia none il discorso `e differente: ogni Virtual Processor `e indipendente dagli altri; qui, per ogni singolo VP possiamo applicare una delle due scelte elencate prima: mantenere il comportamento vecchio, oppure annullarli. Ma nel caso di riconfigurazione non funzionale il comportamento del singolo VP `e rimasto invariato, perci`o abbiamo un passaggio “indolore” da una versione all’altra.

Le tecniche utilizzate nel caso di riconfigurazione non funzionale sono co- munque molto interessanti, e stiamo studiando come applicarle anche nel caso pi`u generale, ovviamente non pi`u in modo automatico, ma aiutati dal- lo sviluppatore, che pu`o fornirci preziose informazioni per l’applicabilit`a di queste tecniche sulla singola applicazione.

5.4.4

Descrivere quando effettuare le riconfigurazioni

Fino a questo momento non abbiamo trattato una questione fondamentale del modello di ASSISTANT: come descrivere quando effettuare le riconfigura- zioni. `E ormai abbastanza chiaro che i differenti comportamenti del modulo sono forniti dal programmatore mediante la definizione di differenti opera- tion, che riprendono la sintassi dei parmod ASSIST: tutti i listati visti fino a questo momento possono essere trasformati in operation senza particolari modifiche. Non abbiamo ancora parlato a fondo, invece, dei meccanismi per modellare il “manager”, necessari per descrivere come e quando decidere di effettuare una riconfigurazione.

OP1 EV2 EV1 EV 1 EV 3 EV 4 OP4 OP3 OP2 EV6 EV4 EV8 EV2

(a) Esempio di un grafo di riconfigurazione

MapOP

FarmOP

EV0: Personal Computer not available

EV1: Memory Requirements unmet

EV0 AND NOT(EV1)

EV0 AND EV 1

NOT(EV0)

(b) Grafo di riconfigurazione che modella il comportamento illustrato in figura 5.6

Figura 5.7: Event-Operation Graph

Parlando dello strato blue dell’applicazione abbiamo gi`a spiegato che in ASSISTANT le riconfigurazioni sono scaturite dagli eventi. Le decisioni, per`o, non dipendono esclusivamente dall’evento ricevuto, ma anche dallo sta- to interno del modulo stesso: primo fra tutti, il comportamento attualmente in esecuzione. Questo ci porta alla definizione di una riconfigurazione in base alla ricezione di un evento e dell’attuale operation attiva. Possiamo quindi modellare l’insieme di possibili riconfigurazioni tramite un grafo, in cui i nodi rappresentano operation e gli archi eventi, esattamente come in figura 5.7a. Gli archi che tornano sulla stessa operation rappresentano le riconfigurazioni non funzionali, quelli tra operation diverse le riconfigurazioni funzionali. Gli eventi possono essere semplici eventi esterni oppure combinazioni di essi.

Dal grafo si pu`o notare la dipendenza tra riconfigurazioni e operation corrente: infatti la ricezione dello stesso evento da due operation diverse pu`o portare a riconfigurazioni differenti.

Inoltre dalla stessa operation possiamo avere pi`u archi con lo stesso even- to. Questo perch´e, come abbiamo gi`a detto, una riconfigurazione `e scaturita dalla ricezione di uno o pi`u eventi, ma determinata anche dallo stato interno del modulo. In base a questo si potrebbe definire, su uno stesso evento, due possibili riconfigurazioni.

Per fare un esempio concreto, di un grafo di riconfigurazioni (chiamato in ASSISTANT event-operation graph) riportiamo il grafo (figura 5.7b) che rappresenta il comportamento del modulo descritto nella figura 5.6 per

presentare le riconfigurazioni funzionali con cambio di forma parallela. In questo caso abbiamo definito due eventi:

1. EV0: Un evento ricevuto quando il computer multicore diventa non

disponibile per il modulo, perch´e non pi`u raggiungibile o perch´e allocato per altre applicazioni.

2. EV1: Un evento ricevuto quando la piattaforma corrente non rispetta

i requisiti di memoria imposti dalla versione farm (la quantit`a di ram di almeno uno dei dispositivi non `e sufficiente).

Il grafo mostra anche l’utilizzo di combinazioni di eventi mediante il costrutto “AND”, e del “NOT” per definire la non presenza di un particolare evento.

Nell’esempio, rispetto a quanto detto discusso nella sezione precedente, abbiamo inserito anche la logica per ritornare all’esecuzione sul quad-core nel caso questo torni disponibile. Inoltre le riconfigurazioni sono pi`u precise: si passa dal Map al Farm se il Personal Computer non `e disponibile e, nello stesso momento, i dispositivi utilizzati rispettano i requisiti di memoria. Al contrario si torna al Map se il PC non `e ancora disponibile e i dispositivi non hanno abbastanza memoria oppure se il PC torna disponibile.

Questi due archi potrebbero essere in realt`a collassati in un arco con even- to EV0 OR EV1. Ma questo grafo viene fornito esplicitamente dal program-

matore, che difficilmente si accorger`a di questa possibilit`a e verosimilmente li terr`a divisi. Comunque al livello del modello o delle prestazioni questo non influisce minimamente.

5.4.5

Analogie con i meccanismi di adattivit`a dei si-