UniTO/anno2/YearI/SecondSem/SCPD/lesson2-01032018.md
Francesco Mecca 5e286062f8 MCAD 2019
2018-11-22 13:09:11 +01:00

90 lines
5.7 KiB
Markdown

# Sistemi di calcolo paralleli e distribuiti
## Misure
* Speedup = Tseq/Tpar(n) (tempo sequenziale / tempo parallelo in *n*)
* Efficienza (derivata da speedup)
* Scalability (weak, strong)
Il tempo parallelo puo' essere dipendente da un algoritmo in cui il parallelismo e' **fisso** (n e' hardcoded) oppure *malleabile*, in cui il grado di parallelismo *n* puo' essere deciso al momento dell'esecuzione. Il grado di parallelismo puo' cambiare anche durante l'esecuzione (*autoscaling*).
### Speedup, Efficienza, Scalabilita'
```
S(n) = Tseq / Tpar(n)
S(n) <= Tseq / (Tseq/n) = n (lo speedup non puo' essere migliore di Tseq/n, speedup ideale = n)
E(n) = S(n) / n (efficienza e' speedup / grado di par.)
Scal(n) = Tpar(1) / Tpar(n) (dove Tpar(1) >= Tseq, se e solo se il codice non ha data races)
```
* Su una macchina multicore, lo speedup mi dice il numero di core massimo che riesco a usare al 100%.
* La *scalabilita'* e' la capacita' di aumentare la performance aumentando il numero di processori. E' una funzione che non e' per forza lineare, associata allo speedup.
### Legge di Amdahl
Da cosa deriva la differenza tra speedup ideale e reale <= ideale?
* **Load balancing**: Supponiamo di dividere un codice in 4 parti: se per eseguirlo sequenzialmente sono richiesti `4x sec`, nel codice parallelo saranno necessari `x`. Come faccio a **bilanciare il carico su tutti i core di esecuzione?** Questo si verifica se l'esecuzione di una delle parti dura di piu' delle altre, lasciando un certo numero di processori in *idle* (sprecando quindi tempo e potenza di calcolo).
* **Extra computation**: Per eseguire codice parallelo di solito si esegue del codice aggiuntivo (piu' complesso) che quindi ha un costo in termini di tempo di esecuzione. Il codice aggiuntivo puo' essere richiesto per distribuire la computazione ai nodi o per mantenerla consistente (necessario ricalcolare anche quello che non si conosce sui singoli core).
* **Comunicazione e sincronizzazione**: Durante l'esecuzione di una computazione parallela, i processi possono essere **indipendenti** (e quindi non richiedere sincronizzazione) oppure (piu' comunemente) **dipendenti** e quindi richiedere una comunicazione tra un processo e l'altro. Questo introduce dei **tempi di attesa** (recv bloccanti sono un esempio).
* **Codice non parallelizzabile**: codice come quello relativo all'I/O e' difficile (se non impossibile) da parallelizzare. Questo dipende dal mezzo da cui devo prendere l'input: es. un disco, che puo' avere accesso parallelo o sequenziale. Se l'accesso e' sequenziale, allora la parallelizzazione e' inutile in quel codice (e puo' portare a race conditions).
Date tutte queste condizioni, la legge di Amdahl dice:
```
T(n) = F*Tseq + ((1-F)*Tseq)/n (F e' la frazione di codice non parallelizzabile)
da cui:
S(n) = Tseq/(F*Tseq + ((1-F)*Tseq)/n) = n / (1+(n-1)*F)
e quindi:
lim[n->inf] S(n) = 1/F
```
Questa formula e' utile per capire che **minimizzando la frazione non parallelizzabile di codice, abbiamo uno speedup migliore**. Essendo che `F==0` non e' idealmente raggiungibile, posso affermare che questo riduce il mio speedup reale.
## Architettura di calcolatori paralleli
Due tipi sono oggetto di studio:
* Shared memory multiprocessor (multicore)
* Distributed memory multicomputer (cluster)
* GPU (piu' avanti)
### Shared memory multiprocessor
Condividendo la memoria tra processori in un architettura di Von Neumann, ottengo l'architettura comunemente chiamata multicore. Su di queste si utilizza solitamente il modello di programmazione *shared memory* (ma e' comunque possibile utilizzare un modello *message passing*, per quanto non sempre necessario.
Il modo migliore per estendere un modello a processore singolo e' avere processori multipli connessi a moduli di memoria multipli, cosi' che ogni processore possa accedere a tutti i moduli. La **rete di interconnessione** dei processori e' la parte piu' costosa / importante di un calcolatore moderno, in quanto e' direttamente responsabile della lettura in memoria, assieme alla *policy* di accesso (e di risoluzione conflitti).
*(vedi slides per rappresentazione grafica, esempi di architetture a memoria condivisa)*
#### Tecniche di programmazione
* Thread-based: il programma e' decomposto in sequenze parallele individuali, che vengono eseguiti nello stesso address space (a differenza dei processi). Questo significa che possono accedere allo stesso spazio di memoria condivisa.
### Distributed memory multicomputer
Macchine separate connesse in rete (ethernet, infiniband, etc). *(slides per esempi di network)*
### Interconnection Networks
Permette comunicazione in varie forme:
* Bus (unico trasporto dati che connette le componenti, non permette accesso multiplo)
* matrici (mesh, griglie) 2/3 dimensionali: architetture array-matrici/cubiche. Hanno la particolarita' che **ogni macchina comunica con un numero costante di macchine**. Array possono essere ciclici (primo connesso con ultimo). Svantaggi: per comunicare con molti degli altri processori sono necessari uno o piu' hop. Scalabile (a differenza della connessione completa). **Indirizzamento** molto semplice.
* Hypercube (connessioni vertice-vertice tra architetture cubiche): architetture 4 dimensionali (non piu' popolari perche' complesse). Struttura *k-ary n-cube*, in cui `k` e' la dimensione del lato del cubo e `n` e' il numero di cubi interconnessi (la dimensione dell'ipercubo). **Indirizzamento**: numero in base `k` con numero cifre == `n`. Vantaggio principale e' che esistono path multipli tra gli stessi processori.
* Switches: (crossbar, trees, multistage interconnected networks)
* Connessione completa (separata) tra ogni oggetti: complessa (O(n^2) connessioni), spesso non praticabile
*(slides per rappresentazioni grafiche)*