multiprocessori a memoria condivisa - di.unito.itgunetti/didattica/architettureii/07... · della...

31
1 1 Multiprocessori a memoria condivisa • Introduzione Sistemi UMA Coerenza della cache Sistemi NUMA Sincronizzazione fra processi Modelli di consistenza della memoria Sistemi COMA 2 Multiprocessori a memoria condivisa Una architettura con più CPU che condividono lo stessa memoria primaria viene detto multiprocessore. In un sistema multiprocessore tutti i processi che girano sulle varie CPU condividono un unico spazio di indirizzamento logico, mappato su una memoria fisica che può però anche essere distribuita fra i vari processori. Ogni processo può leggere e scrivere un dato in memoria semplicemente usando una load o una store, e la comunicazione fra processi avviene attraverso la memoria condivisa. E’ responsabilità dell’hardware del sistema fare in modo che tutte le CPU possano vedere e usare la stessa memoria principale. 3 Multiprocessori a memoria condivisa E’ un modello architetturale concettualmente semplice, comodo da usare per i programmatori, e applicabile ad una vasta gamma di problemi che possano essere modellati come una serie di task eseguibili almeno in parte parallelamente (Tanenbaum, Fig. 8.17).

Upload: dinhtu

Post on 17-Feb-2019

219 views

Category:

Documents


0 download

TRANSCRIPT

1

1

Multiprocessori a memoria condivisa

•  Introduzione •  Sistemi UMA •  Coerenza della cache •  Sistemi NUMA •  Sincronizzazione fra processi •  Modelli di consistenza della memoria •  Sistemi COMA

2

Multiprocessori a memoria condivisa

•  Una architettura con più CPU che condividono lo stessa memoria primaria viene detto multiprocessore.

•  In un sistema multiprocessore tutti i processi che girano sulle varie CPU condividono un unico spazio di indirizzamento logico, mappato su una memoria fisica che può però anche essere distribuita fra i vari processori.

•  Ogni processo può leggere e scrivere un dato in memoria semplicemente usando una load o una store, e la comunicazione fra processi avviene attraverso la memoria condivisa.

•  E’ responsabilità dell’hardware del sistema fare in modo che tutte le CPU possano vedere e usare la stessa memoria principale.

3

Multiprocessori a memoria condivisa •  E’ un modello architetturale concettualmente semplice, comodo da

usare per i programmatori, e applicabile ad una vasta gamma di problemi che possano essere modellati come una serie di task eseguibili almeno in parte parallelamente (Tanenbaum, Fig. 8.17).

2

4

Multiprocessori a memoria condivisa •  Siccome tutte le CPU vedono lo stesso spazio di indirizzamento, è

sufficiente una copia del sistema operativo.

•  Quando un processo termina o va in wait per qualche ragione, il S.O. può cercare nella nella coda di ready un altro processo a cui dare la CPU idle.

•  Al contrario, nei sistemi a memoria non condivisa, ogni CPU deve far girare la propria copia del sistema operativo, e i processi possono comunicare solo attraverso lo scambio di messaggi.

•  Il problema di fondo dei sistemi multiprocessore a memoria condivisa è la memoria stessa, che è difficile da far funzionare in maniera efficiente quanto più è alto il numero dei processori coinvolti.

5

•  Tutti i moderni SO (Windows, Solaris, Linux, MacOS) prevedono in particolare la cosiddetta multielaborazione simmetrica (symmetric multiprocessing, SMP), in cui (tralsciando un po’ di cose) uno scheduler gira su ciascun processore.

•  I processi “ready to run” possono essere inseriti in un’unica coda, vista da ciascun scheduler, oppure vi può essere una coda “ready to run” separata per ciascun scheduler/processore.

•  Quando lo scheduler di un processore si attiva, sceglie uno dei processi “ready to run” e lo manda in esecuzione sul proprio processore (nel caso di coda unica le cose sono un po’ più delicate, perché?)

Multiprocessori a memoria condivisa

6

•  Un aspetto importante dei sistemi multiprocessore è il bilanciamento del carico.

•  Non ha infatti senso avere un sistema con più CPU se poi i vari processi da eseguire non sono distribuiti più o meno omogeneamente tra i vari processori.

•  Nel caso di un’unica coda ready to run, il bilanciamento del carico è solitamente automatico: quando un processore è inattivo, il suo scheduler prenderà un processo dalla coda comune e lo manderà in esecuzione su quel processore.

Multiprocessori a memoria condivisa

3

7

•  I SO moderni predisposti per l’SMP usano però spesso una coda separata per ciascun processore (per evitare i problemi connessi alla coda unica, accennati prima).

•  Esiste allora un esplicito meccanismo di bilanciamento del carico che può prendere un processo in attesa sulla coda di un processore sovraccarico e spostarlo nella coda di un processore scarico.

•  Ad esempio, Linux SMP attiva il proprio meccanismo di bilanciamento del carico ogni 200 millisecondi, e ogni qualvolta si svuota la coda di un processore.

Multiprocessori a memoria condivisa

8

•  Notate che la migrazione di un processo da un processore ad un altro può non essere conveniente nel caso in cui ciascun processore abbia una cache privata (perché?).

•  Per questa ragione, alcuni SO, come Linux, mettono a disposizione delle system call per poter specificare che un processo non deve cambiare processore, indipendentemente dal carico del processore stesso.

•  Si distinguono sostanzialmente tre classi di multiprocessore, a seconda del modo in cui ogni CPU vede la memoria principale:

Multiprocessori a memoria condivisa

9

Multiprocessori a memoria condivisa 1.  Uniform Memory Access (UMA): Questo tipo di architettura viene

così chiamata perché tutti i processori condividono un’unica memoria primaria centralizzata, e quindi ogni CPU ha lo stesso tempo di accesso alla memoria.

•  A causa del tipo di architettura, questi sistemi vengono anche chiamati Symmetric Shared-memory Multiprocessors (SMP) (H-P3, Fig. 6.1)

4

10

Multiprocessori a memoria condivisa 2.  Non Uniform Memory Access (NUMA): in questi sistemi i vari

processori vedono ancora uno spazio di indirizzamento logico unico, ma la memoria fisica è distribuita fra le varie CPU, e quindi i tempi di accesso ai dati variano a seconda che siano nella RAM locale o in una remota (da cui appunto il termine NUMA)

•  Questi sistemi vengono quindi anche chiamati Distributed Shared Memory (DSM) architectures (H-P3, Fig. 6.2)

11

Multiprocessori a memoria condivisa

3.  Cache Only Memory Access (COMA): in cui i dati non hanno un luogo (ossia uno specifico indirizzo di memoria) in cui risiedono permanentemente, e da cui vengono letti (ossia copiati nelle cache locali) e/o modificati (ossia modificati nella cache e poi aggiornati al loro indirizzo di “residenza”).

•  Invece, i dati possono migrare e/o essere replicati nei vari banchi di memoria che formano la memoria centrale del sistema.

12

Multiprocessori di tipo UMA •  La forma più semplice di sistema multiprocessore prevede un unico

bus su cui si affacciano almeno due CPU e una memoria (condivisa da tutti i processori).

•  Quando una CPU vuole leggere una locazione di memoria verifica prima che il bus sia libero, invia la richiesta al modulo di interfaccia della memoria e attende sul bus che arrivi il valore richiesto.

•  La memoria condivisa può però facilmente diventare un collo di bottiglia per le prestazioni del sistema, visto che tutti i processori devono sincronizzarsi sull’uso di un singolo bus e memoria.

5

13

Multiprocessori di tipo UMA •  Notiamo che quando parliamo di processore/CPU, ormai da qualche

anno si sottointende che sia un processore multi-core, ossia dotato di almeno due (ma anche 4, 6 8) unità indipendenti di esecuzione dei programmi, ognuna chiamata core.

•  Sugli n core possono girare in parallelo n processi, uno per core. Ogni core può poi a sua volta essere multi-threaded, e quindi può eseguire in parallelo le istruzioni appartenenti a due o più peer thread.

•  Un processore n-core è di fatto un sistema multiprocessore di tipo UMA, in cui la prima cache condivisa (L2 o L3) da tutti i core costituisce il canale di comunicazione.

•  Un sistema multiprocessore può quindi essere costituito da più processori/CPU ciascuno dei quali multi-core.

14

Multiprocessori di tipo UMA •  Per semplicità, nel seguito continuiamo a pensare ogni CPU di un

sistema multiprocessore come CPU single-core.

•  Il problema delle comunicazioni con la RAM è mitigato dalla presenza delle cache di ogni CPU. Se poi ogni CPU ha anche una memoria privata utilizzata per contenere quei dati che non devono essere condivisi dagli altri processori, allora il traffico da e verso la memoria condivisa può ridursi sensibilmente (Tanenbaum, Fig. 8.24)

15

Multiprocessori UMA: snooping caches

•  La presenza di cache locali pone comunque un problema fondamentale: la visione che ogni processore ha della memoria passa attraverso la propria cache, e due processori possono vedere valori diversi per la stessa locazione (H-P3, Fig. 6.7)

Time Event Cache A Cache B RAM location for X

0 1

1 CPU A reads X 1 1

2 CPU B reads X 1 1 1

3 CPU A stores 0 into X 0 1 0

6

16

Snooping Caches •  Questo è il problema della coerenza della cache, e senza una

adeguata soluzione non permette l’uso di cache nei vari processori, con pesanti riflessi negativi sulle prestazioni.

•  Sono stati proposti vari protocolli di coerenza della cache, e tutti hanno come scopo quello di evitare che versioni differenti della stessa linea di RAM possano essere contemporaneamente presenti in due o più cache (un problema noto come false sharing).

•  le soluzioni usate sono tutte implementate a livello hardware: il controller di ogni cache è in grado di monitorare, sul bus, tutte le richieste alla memoria che provengono dalle altre CPU, e se è il caso, il protocollo di coerenza interviene.

•  Si dice che i controller delle cache eseguono lo snooping del bus

17

Snooping Caches •  Un semplice protocollo di coerenza della cache è il write through.

Consideriamo innanzitutto gli eventi che si possono verificare tra un processore che accede a dei dati, e la sua cache:

•  read miss: il cache controller della CPU preleva la linea mancante dalla RAM e la mette nella cache. Ulteriori letture dello stesso dato avverranno nella cache (quindi, le successive letture saranno dei read hit).

•  write miss: il dato modificato viene direttamente scritto in RAM: la linea contenente il dato non viene prima caricata nella cache locale.

•  write hit: la cache line viene aggiornata e l’aggiornamento viene anche propagato alla RAM.

•  Notate che le operazioni di scrittura vengono propagate alla RAM, il cui contenuto rimane quindi sempre aggiornato.

18

Snooping Caches •  Consideriamo ora le cose dal punto di vista dello snooper di un’altra

CPU (colonna di destra della tabella). Sia cache 1 la cache che genera le read/write, e cache 2 la snooping cache (Tanenbaum, Fig. 8.25).

•  read miss: la cache 2 vede cache 1 prelevare una linea dalla memoria, ma non fa nulla (notate, in caso di read hit la cache 2 non se ne accorge nemmeno)

•  write miss/hit: cache 2 verifica se ha una copia del dato modificato: in caso negativo, non fa nulla. Se però il dato è presente, la linea che lo contiene viene marcata come invalida nella cache 2.

7

19

Snooping Caches •  Poiché tutte le cache sorvegliano tutte le operazioni compiute dalle

altre cache in memoria, quando una cache modifica un dato, la modifica viene fatta nella cache stessa (se c’è), in memoria, e in più la “vecchia” linea viene rimossa da tutte le altre cache (in realtà viene semplicemente marcata invalida).

•  In questo modo, nessuna cache può contenere dati inconsistenti rispetto alle altre cache.

•  Ovviamente, esistono varianti a questo protocollo di base. Ad esempio, le linee “vecchie” potrebbe essere immediatamente aggiornate al nuovo valore, anziché essere marcate invalide.

•  Questa variante richiede più lavoro, ma previene il verificarsi di futuri cache miss

20

Snooping Caches •  Il pregio fondamentale di questo protocollo di coerenza della cache

è la semplicità

•  Il problema fondamentale dei protocolli write-through based è l’inefficienza, visto che ogni operazione di write viene propagata alla RAM, e il bus di comunicazione può facilmente divenire un collo di bottiglia.

•  Per limitare questo problema, nei protocolli write-back based non tutte le write vengono immediatamente propagate in RAM: un bit nella cache viene settato per indicare che la linea nella cache è aggiornata, mentre in RAM è “vecchia”.

•  Prima o poi, la linea modificata verrà propagata in RAM, ma possibilmente solo dopo molte modifiche (e non dopo ognuna).

21

Il Protocollo MESI •  Uno dei più diffusi protocolli di coerenza della cache di tipo write-

back, usato da molti processori moderni per monitorare il bus è il MESI, in cui ogni entry della cache può essere in uno di 4 possibili stati:

1.  Invalid l’entry della cache non contiene dati validi

2.  Shared Più cache possono contenere la linea, e la memoria RAM è aggiornata

3.  Exclusive Nessun’altra cache contiene la linea, e la memoria RAM è aggiornata.

4.  Modified La linea è valida, la memoria RAM contiene un valore vecchio, e non esistono altre copie.

8

22

Il Protocollo MESI •  Quando il sistema viene fatto partire, tutte le entry delle cache sono

marcate I: invalid.

•  La prima volta che una linea viene letta nella cache di CPU 1 viene marcata E: exclusive, poiché è l’unica cache a contenere quella linea.

•  Successive letture del dato da parte della stessa CPU useranno la copia in cache, e non impegneranno il bus. (Tanenbaum, Fig. 8.26a)

23

Il Protocollo MESI •  Se CPU 2 legge la stessa linea, lo snooper di CPU 1 se ne accorge e

annuncia sul bus che anche CPU 1 ha una copia della linea. Entrambe le entry nelle cache vengono marcate S: Shared.

•  Successive letture del dato da parte di CPU 1 o CPU 2 avverranno nelle rispettive cache, e non useranno il BUS (Tanenbaum, Fig. 8.26b)

A

24

Il Protocollo MESI •  Se CPU 2 modifica una linea marcata S, invia sul bus un segnale di

invalidazione della linea, in modo che le altre CPU possano invalidarla nella loro cache. La linea viene marcata M: modified, e non viene scritta in RAM. (notate che se la linea era marcata E, il segnale di avviso alle altre CPU non è necessario, perché non esistono altre copie della linea in altre cache) (Tanenbaum, Fig. 8.26c).

9

25

Il Protocollo MESI

•  Cosa succede se CPU 3 tenta di leggere la stessa linea? Lo snooper di CPU 2 se ne accorge, sa di possedere l’unica copia valida della linea, per cui invia un segnale su bus per avvertire CPU 3 di aspettare, mentre la copia valida viene usata per aggiornare memoria.

•  A fine aggiornamento CPU 3 può prelevare la linea richiesta, e nelle due cache la linea viene marcata S, shared (Tanenbaum, Fig. 8.26.d).

26

Il Protocollo MESI

•  Se a questo punto CPU 2 rimodifica la linea, ovviamente nella sua cache, invierà di nuovo un segnale di invalidazione sul bus, e tutte le altre copie (ad esempio quella di CPU 3) vengono marcate come I: invalid. La linea nella cache di CPU 2 è di nuovo marcata M: modified. (Tanenbaum, Fig. 8.26e).

27

Il Protocollo MESI •  Se, infine, CPU 1 cerca di scrivere un dato nella linea, CPU 2 vede

il tentativo di write e invia un segnale sul bus per dire alla CPU 1 di aspettare mentre aggiorna la linea in memoria. Alla fine, CPU 2 marca la propria copia della linea come invalida, perché sa che un’altra CPU sta per modificarla.

•  A questo punto siamo nella situazione in cui CPU 1 sta scrivendo in una linea che non è in nessuna cache.

•  Se si sta usando una politica write-allocate, la linea sarà caricata nella cache di CPU 1 e marcata M (Tanenbaum, Fig. 8.26f).

•  Se non si sta usando una politica write-allocate, la write ha effetto direttamente in RAM, e la linea continua a non essere in nessuna cache.

10

28

Sistemi UMA a commutatori incrociati (crossbar switch)

•  Anche usando un protocollo come il MESI, l’uso di un bus singolo su cui si affacciano tutti i processori e la memoria limita la dimensione di sistemi multiprocessore UMA ad un massimo che di solito si indica in 32 CPU.

•  Per andare al di là di questo limite, è necessario usare un diverso sistema di interconnessione tra le CPU e la RAM. Lo schema più semplice per connettere n CPU a k memorie è a commutatori incrociati (crossbar switch), un sistema simile a quello usato per decenni nelle centrali telefoniche.

29

UMA a crossbar switch •  Ad ogni intersezione di una linea di comunicazione orizzontale e

verticale è posizionato uno switch, che può connettere (o meno) le due linee.

•  Nell’esempio, tre switch sono chiusi, e connettono le coppie CPU-memoria (001-000), (101-101) e (110-010). (Tanenbaum, Fig. 8.27)

30

UMA a crossbar switch

•  Naturalmente, è possibile configurare gli switch in modo che ciascuna CPU possa connettersi a ciascun banco di memoria (che è poi quello che rende il sistema un UMA)

•  Il numero di switch necessari per realizzare questo schema però cresce quadraticamente col numero di CPU (memorie) coinvolte: n CPU ed n memorie richiedono n2 switch.

•  La cosa è accettabile per sistemi di media grandezza (vari sistemi multiprocessore della Sun usano questo schema), ma certamente non è usabile in un sistema con, ad esempio, 256 CPU (sarebbero necessari 2562 switch).

11

31

Sistemi UMA a reti con commutatori a più stadi

•  Per connettere molte CPU si può usare un sistema basato su semplici switch bidirezionali con due ingressi e due uscite: in questi switch ciascun ingresso può essere rediretto su ciascuna uscita (Tanenbaum, Fig. 8.28):

32

Sistemi UMA a reti con commutatori a più stadi

•  I messaggi scambiati tra CPU e memoria sono fatti di quattro parti:

•  Module: quale memoria usare –quale CPU richiede il dato

•  Address: specifica un indirizzo all’interno del modulo di memoria;

•  Opcode: l’operazione da eseguire (come READ o WRITE);

•  Value (opzionale): il valore da scrivere in caso di WRITE.

•  Lo switch può essere programmato in modo da analizzare il Module e determinare su quale output instradare il messaggio

33

Sistemi UMA a reti con commutatori a più stadi

•  Gli switch 2 x 2 possono essere usati in molti modi per costruire network a commutazione a più stadi. Un semplice esempio è il modello di rete omega (Tanenbaum, Fig. 8.29):

12

34

Sistemi UMA a reti con commutatori a più stadi

•  Nell’esempio, 8 CPU sono connesse a 8 memorie, usando in tutto 12 switch in tre stadi. In generale, n CPU ed n memorie richiedono log2n stadi, con n/2 switch per stadio, per un totale di (n/2)log2n switch: molto meglio che nel caso dei crossbar swtich (n2)

•  Vediamo un esempio di funzionamento di questa rete. La CPU 011 vuole leggere un dato nel modulo di RAM 110. La CPU invia una READ allo switch 1D con Module = 110 -- 011.

•  Lo switch preleva il bit più significativo (quello più a sinistra) e lo usa per l’instradamento: 0 instrada la richiesta sull’output superiore, 1 instrada la richiesta sull’output inferiore.

•  Nel nostro esempio la richiesta viene instradata verso 2D.

35

Sistemi UMA a reti con commutatori a più stadi

•  Lo switch 2D si comporta allo stesso modo: analizza il secondo bit più significativo (quello centrale) e instrada la richiesta verso 3D.

•  Infine, il bit meno significativo viene usato per l’ultimo instradamento, verso il modulo 110 (percorso a nella figura)

•  A questo punto, il dato letto deve essere reinstradato alla CPU 011: viene usato il suo “indirizzo”, leggendo però i bit da destra verso sinistra.

•  Allo stesso tempo, la CPU 001 vuole eseguire una WRITE nel modulo 001. Avviene un processo simile a quello visto (percorso b nella figura). Siccome i percorsi a e b non usano gli stessi switch, le due richieste possono procedere in parallelo.

36

Sistemi UMA a reti con commutatori a più stadi

•  Consideriamo invece cosa accade se la CPU 000 vuole accedere il modulo 000. La sua richiesta confliggerebbe con la richiesta della CPU 001 sullo switch 3A: una delle due richieste deve attendere.

•  Al contrario di quello che accade con le reti che usano crossbar switch, le reti omega sono reti bloccanti: non tutte le sequenze di richieste possono essere servite contemporaneamente.

•  I conflitti possono verificarsi sull’uso di una connessione, di uno switch, o in una richiesta alla memoria o una risposta ad una CPU.

•  Varie tecniche possono essere usate per connettere CPU e memorie in modo da minimizzare la possibilità di conflitti e massimizzare il parallelismo delle comunicazioni CPU-memoria.

13

37

Multiprocessori NUMA

•  I sistemi UMA a bus singolo sono limitati dal numero di processori, e per connettere più processori è necessario dell’hardware comunque costoso. Allo stato attuale, non è conveniente costruire sistemi UMA con più di 256 processori.

•  Per costruire sistemi più grandi è necessario accettare un compromesso: che non tutti i moduli di memoria abbiano lo stesso tempo di accesso rispetto a ciascuna CPU.

•  Questo è la caratteristica di base da cui prendono il nome i sistemi NUMA: Non Uniform Memory Access.

38

Multiprocessori NUMA •  Come per i sistemi UMA, nei sistemi NUMA tutte le CPU vedono

lo stesso spazio di indirizzamento ma, ogni processore è dotato di una sua propria memoria locale, vista anche da tutti gli altri processori.

•  Al contrario dei sistemi UMA quindi, nei sistemi NUMA l’accesso ai moduli di memoria locale è più veloce dell’accesso ai moduli di memoria remoti.

•  Una conseguenza di questa caratteristica è che i programmi scritti per sistemi UMA possono comunque girare senza dover apportare alcun cambiamento su macchine NUMA, possibilmente con prestazioni diverse a causa dei diversi tempi di accesso ai vari moduli remoti di RAM (ovviamente a parità di tutte le altre condizioni)

39

Multiprocessori NUMA

•  Poiché le macchine NUMA hanno un unico spazio di indirizzamento logico visto da tutte le CPU, mentre la memoria fisica è in realtà suddivisa tra i vari processori, emerge il concetto di memoria locale e remota.

•  Tuttavia, anche l’accesso alla memoria remota da parte di ciascuna CPU avviene mediante LOAD e STORE

•  Esistono due tipi di sistemi NUMA:

•  Non-Caching NUMA (NC-NUMA)

•  Cache-Coherent NUMA (CC-NUMA)

14

40

Multiprocessori NC-NUMA •  In un sistema NC-NUMA i processori non hanno cache locale.

•  Ogni accesso alla memoria è gestito da una MMU modificata, che controlla se la richiesta è diretta alla memoria locale o a un modulo remoto, nel qual caso la richiesta viene instradata al nodo contenente il dato richiesto.

•  Evidentemente, programmi che usano dati remoti (rispetto alla CPU su cui girano) risulteranno molto più lenti che se i dati fossero memorizzati nella memoria locale del processore su cui girano (Tanenbaum, Fig. 8.30).

rete di interconnessione a topologia non specificata

41

Multiprocessori NC-NUMA •  Ovviamente, nei sistemi NC-NUMA il problema della coerenza

della cache è automaticamente risolto perché non c’e’ nessuna forma di caching: ogni dato della memoria è presente esattamente in una locazione ben precisa.

•  Rimane il problema dell’inefficienza dell’accesso alla memoria remota. Per questo, le macchine NC-NUMA possono usare software elaborato per spostare le pagine di memoria da un modulo all’altro in modo da massimizzare le prestazioni.

•  Un demone page scanner può attivarsi ogni pochi secondi, esaminare le statistiche sull’indirizzamento della memoria, e spostare le pagine da un modulo all’altro per cercare di migliorare le prestazioni.

42

Multiprocessori NC-NUMA •  In realtà, nei sistemi NC-NUMA, ogni processore può avere anche

una memoria locale privata e una cache, e solo i dati privati del processore (ossia quelli nella memoria locale privata) possono risiedere nella cache.

•  Questa soluzione aumenta ovviamente le prestazioni di ciascun processore, ed è adottata ad esempio nel Cray T3D/E.

•  Tuttavia, il tempo di accesso ai dati remoti rimane molto alto, nel Cray T3D/E è di 400 cicli di clock del processore, contro solo due cicli necessari per accedere un dato nella cache locale.

15

43

Multiprocessori CC-NUMA •  Aggiungere il caching diminuisce ovviamente il i tempi di accesso

ai dati remoti, ma introduce il problema della coerenza della cache.

•  Un modo di garantire la coerenza sarebbe ovviamente lo snooping sul bus di sistema, ma questa tecnica diventa troppo inefficiente oltre un certo numero di CPU, ed è comunque troppo difficile da implementare nei sistemi che non usano un bus comune di interconnessione. Abbiamo allora bisogno di un approccio diverso.

•  L’approccio più usato per costruire sistemi CC-NUMA con molte CPU assicurando la coerenza della cache è noto come schema o protocollo directory-based (multiprocessor).

•  L’idea di base è di associare ad ogni nodo del sistema una directory per le linee della sua RAM: un database che dice in quale cache si trova ogni linea, e qual è il suo stato.

44

Multiprocessori CC-NUMA •  Quando viene indirizzata una linea di memoria, la directory del

nodo a cui quella linea appartiene viene interrogata per sapere se la linea si trova in qualche cache e se questa sia stata modificata rispetto alla copia in RAM.

•  Poiché una directory viene interrogata ogni volta che una istruzione accede la corrispondente memoria, deve essere implementata con un hardware molto veloce, ad esempio una memoria associativa, o per lo meno della RAM statica.

•  Come esempio di protocollo directory based, consideriamo un sistema con 256 nodi, ognuno formato da una CPU e una RAM locale da 16 MB.

•  Ci sono in tutto 232 = 4 GB di RAM, e ogni nodo contiene 218 linee da 64 byte ciascuna (218 x 26 = 224 = 16 MB).

45

Multiprocessori CC-NUMA •  Lo spazio di indirizzamento è unico, col nodo zero che contiene la

memoria con indirizzi da 0 a 16 MB, il nodo 1 la memoria con indirizzi da 16 a 32 MB, e così via.

•  Un indirizzo fisico è quindi scritto su 32 bit:

•  gli 8 bit più significativi indirizzano di fatto il numero del nodo a cui appartiene il banco di RAM che contiene il dato indirizzato.

•  i successivi 18 bit indicano la linea indirizzata all’interno del banco da 16 MB

•  i restanti 6 bit meno significativi indirizzano il byte all’interno della linea (Tanenbaum, Fig. 8.31b):

line

16

46

Multiprocessori CC-NUMA •  Assumiamo che i nodi siano connessi da una rete con topologia non

ulteriormente specificata. Ogni nodo contiene anche una directory con 218 entry per tenere traccia delle linee del corrispondente modulo locale di memoria.

•  Ogni entry della directory registra se la corrispondente linea è memorizzata in qualche cache, e se si nella cache di quale nodo.

•  Per semplicità assumiamo che ogni linea da 64 byte sia contenuta al massimo in una cache di qualche processore (Tanenbaum, Fig. 8.31a).

47

Multiprocessori CC-NUMA •  Come esempio di funzionamento, vediamo cosa accade quando la

CPU 20 esegue una LOAD, specificando quindi un indirizzo di RAM

•  La CPU 20 presenta l’indirizzo alla propria MMU, la quale traduce la LOAD in un indirizzo fisico, ad esempio, 0x24000108.

•  La MMU divide l’indirizzo i 3 parti, che in decimale sono (verificate che i numeri siano giusti):

–  nodo 36

–  linea 4

–  offset 8

•  La MMU vede quindi che il dato indirizzato sta al nodo 36, e invia una richiesta attraverso la rete al nodo 36, per sapere se la linea 4 è in una cache, e se si in quale.

48

Multiprocessori CC-NUMA

•  Il nodo 36 instrada la richiesta alla propria directory la quale verifica però che la linea non è nella cache di qualche nodo remoto

•  la linea allora viene prelevata dalla RAM locale e inviata al nodo 20, mentre la directory viene aggiornata per indicare che ora la linea 4 è in cache al nodo 20 (Tanenbaum, Fig. 8.31c).

1 20

la directory del nodo 36

17

49

Multiprocessori CC-NUMA •  Consideriamo invece il caso in cui la richiesta riguarda la linea 2 del

nodo 36. in questo caso, la directory del nodo 36 verifica che la linea è nella cache del nodo 82.

•  La directory del nodo 36 deve allora aggiornare la entry della linea 2, per indicare che la linea è ora al nodo 20, e inviare un messaggio al nodo 82 richiedendo che la linea 2 venga inviata al nodo 20 e che la corrispondente entry nella cache del nodo 82 venga invalidata.

•  Ma quando vengono aggiornate le linee in RAM? Ovviamente solo quando vengono modificate. La soluzione più semplice è farlo quando una CPU esegue una STORE: la modifica viene propagata alla RAM che contiene la linea indirizzata dalla STORE.

50

Multiprocessori CC-NUMA •  Si può osservare che una architettura di questo tipo, indicata come

un “multiprocessore a memoria condivisa” vede in realtà molti messaggi attraversare la rete di interconnessione.

•  L’overhead di un tale sistema è tutto sommato accettabile. Ogni nodo ha 16 MB di RAM, e 218 entry da 9 bit per tenere traccia dello stato delle linee (perché 9?)

•  L’overhead è quindi 9 x 218 bit / 16 MB, ossia circa 1,76 %, il che è certamente accettabile (anche se la directory va implementata con memoria ad alta velocità, il che aumenta i costi).

•  Nel caso di linee da 32 byte l’overhead sale al 4%, e scende sotto l’1% nel caso di linee da 128 byte.

51

Multiprocessori CC-NUMA •  In un sistema reale, questa architettura directory based sarà

sicuramente più complessa:

1.  nell’esempio, una linea può essere al massimo in una cache, e si può aumentare l’efficienza del sistema permettendo che le linee di memoria possano essere contemporaneamente nelle cache di più nodi.

2.  tenendo traccia del fatto che una cache line è stata modificata o meno, si possono limitare le comunicazioni fra CPU e memorie.

•  Ad esempio, se una cache line non è stata modificata, la linea originale in RAM è ancora valida, e una read da una CPU remota per quella line può essere soddisfatta dalla RAM stessa, senza dover andare a recuperare la linea dalla cache che ne contiene una copia (visto che le due copie sono identiche)

18

52

Sincronizzazione tra processi •  In un sistema monoprocessore, i vari processi si sincronizzano fra

loro usando opportune system call o costrutti del linguaggio in uso: semafori, regioni critiche condizionali, monitors.

•  Questi meccanismi di sincronizzazione sono costruiti a partire da opportune primitive di sincronizzazione hardware: spesso una istruzione macchina non interrompibile in grado di prelevare e modificare un valore, o di scambiare il contenuto di un registro e una cella di memoria.

•  In un sistema multiprocessore abbiamo bisogno di primitive di sincronizzazione simili: i processi vedono un unico spazio di indirizzamento e la sincronizzazione deve avvenire sfruttando questo spazio comune, e non meccanismi a scambio di messaggi.

53

Sincronizzazione tra processi •  Ecco una classica soluzione al problema della sezione critica per un

sistema monoprocessore che usa una operazione di exchange atomica. Può essere poi usata per implementare semafori o altre primitive di sincronizzazione ad alto livello:

Shared var int lock = 0; // lock inizialmente accessibile int v = 1; repeat

while ( v == 1) do exch (v, lock); //entry section critical section // qui dentro lock = 1 lock = 0; // exit section altro codice non in mutua esclusione // il lock è di nuovo libero

until false;

•  questo tipo di lock è detto spin lock, perché il processo cicla sulla variabile di lock fino a quando non la trova libera.

54

Sincronizzazione tra processi •  Tuttavia, in un ambiente in cui i processi che tentano di

sincronizzarsi su una variabile condivisa possono girare su CPU diverse, l’atomicità delle istruzioni di sincronizzazione non è sufficente.

•  Su un processore, l’istruzione atomica sarà eseguita senza interruzioni, ma che succede sugli altri processori?

•  Avrebbe senso disabilitare tutte le operazioni sulla memoria dal momento in cui una primitiva di sincronizzazione viene avviata a quando ha finito di modificare la variabile di sincronizzazione?

•  Funzionerebbe, ma questo rallenterebbe le operazioni delle CPU non coinvolte nella sincronizzazione. (e notate che stiamo anche ignorando l’esistenza della cache di ciascun processore...)

19

55

Sincronizzazione tra processi •  La soluzione adottata in molti processori usa una coppia di

istruzioni, eseguite una dopo l’altra.

•  La prima istruzione cerca di portare in CPU il valore della variabile condivisa usata da tutti i processori per la sincronizzazione.

•  La seconda istruzione cerca di modificare la variabile condivisa, e restituisce un valore da cui si può capire se la coppia di istruzioni è stata eseguita in modo atomico, il che in un sistema multiprocessore, significa:

1.  nessun altro processo ha modificato il valore della variabile usata per la sincronizzazione prima della terminazione della seconda istruzione della coppia, e:

2.  Non si è verificato un context switch nel processore tra le due istruzioni.

•  Sia [0(R1)] il valore della cella di memoria di indirizzo 0(R1), usata come variabile condivisa di sincronizzazione.

56

Sincronizzazione tra processi •  1) LL R2, 0(R1) // Load linked: scrive [0(R1)] in R2

2) SC R3, 0(R1) // Store conditional: scrive il contenuto // di R3 in 0(R1)

•  L’esecuzione delle due istruzioni rispetto a 0 (R1) è vincolata a ciò che accade tra l’esecuzione delle due istruzioni:

1.  Se 0 (R1) viene modificata (da un altro processo) prima dell’esecuzione della SC, la SC “fallisce”, ossia: 0 (R1) non viene modificata dalla SC e viene scritto 0 in R3. Se invece la SC non fallisce, allora: R3 viene copiato in 0(R1) e in R3 viene scritto 1.

2.  Analogamente, se la CPU su cui la coppia di istruzioni viene eseguita esegue un context switch tra le due istruzioni, la SC fallisce (con gli stessi effetti del punto 1).

57

Sincronizzazione tra processi •  Ed ecco come si può realizzare una exchange “atomica” tra R4 e

0( R1) in un sistema multiprocessore a memoria condivisa (notate che usiamo qualcosa di simile ad uno spin lock):

retry: OR R3, R4, R0 // copy exchange value R4 in R3 LL R2, 0(R1) // load linked: copy [0(R1)] in R2 SC R3, 0(R1) // try to store exchange value in 0(R1) BEQZ R3, retry // spin if store failed MOV R4, R2 // now put loaded value in R4

•  Quando la MOV viene eseguita, R4 e 0(R1) sono stati scambiati in modo “atomico”, e si è certi che il contenuto di 0(R1) non è stato modificato da altri processi prima del completamento dell’exchange. Chiamiamo EXCH l’operazione eseguita da questo codice.

20

58

Sincronizzazione tra processi •  Una volta in possesso di un’operazione atomica EXCH, la si può

usare per implementare spin locks: accessi ad una sezione critica che ogni processore cerca di acquisire ciclando sulla variabile di lock, che controlla appunto l’accesso mutuamente esclusivo.

•  La variabile di lock settata a 0 o a 1 indicherà se la sezione critica è libera o occupata da un altro processo.

•  Dal corso di Sistemi Operativi sappiamo che l’uso del busy waiting per implementare sezioni critiche è accettabile solo le sezioni critiche sono molto brevi.

•  Sezioni critiche molto brevi possono poi essere sfruttate per implementare i meccanismi di sincronizzazione e mutua esclusione ad alto livello, ad esempio i semafori.

•  Ma in ogni caso, il problema del busy waiting è meno grave nei sistemi multiprocessore. Perché?

59

Sincronizzazione tra processi •  Se non ci fosse la cache (e quindi nessun problema di coerenza della

cache), la variabile di lock andrebbe tenuta in memoria: un processore cerca di acquisire il lock con una exchange atomica, e verifica se il lock è libero.

•  Ricordate: [0(R1)] = 0 = lock libero; [0(R1)] = 1 = lock occupato

ADD R2, R0, #1 // inizializza il valore di supporto in R2

lockit: EXCH R2, 0(R1) // exchange atomico

BNEZ R2, lockit // ritenta se [0(R1)] = 1 = locked sezione critica

•  Per rilasciare il lock (ossia uscire dalla sezione critica) il processore scriverà 0 in 0(R1).

60

Sincronizzazione tra processi •  Se il sistema supporta la coerenza della cache, allora la variabile di

lock può essere mantenuta nella cache di ciascun processore.

•  Questo rende il meccanismo dello spin lock più efficiente, perché tutte le operazioni dei vari processori che cercano di acquisire il lock operano sulle rispettive cache, in modo più efficiente.

•  Dobbiamo però modificare leggermente la procedura di spin lock: ogni processore esegue una read sulla copia in cache del lock, fino a quando non vede che il lock è libero.

•  A questo punto tenta di acquisire il lock (cioé entrare in sezione critica) usando la exchange atomica.

21

61

Sincronizzazione tra processi lockit: LD R2, 0(R1) // load of lock

BNEZ R2, lockit // lock not available, spin again ADD R2, R0, #1 // prepare value for locking EXCH R2, 0(R1) // atomic exchange BNEZ R2, lockit // spin if lock was not 0

•  Un esempio di funzionamento con tre CPU che usano il MESI è riportato nel lucido seguente (H-P3, Fig. 6.37): una volta che CPU 0 scrive 0 nel lock, l’entry delle altre due cache viene invalidata, e il nuovo valore deve essere prelevato dalla cache di CPU 0.

•  Una delle due cache riceve per prima il valore 0 e riesce ad eseguire la exchange, mentre l’altro processore troverà la variabile di lock già a 1 e dovrà ricominciare lo spinning.

62

CPU P0

CPU P1 CPU P2 coherence state of lock

Bus activity

1 has lock

spins, testing if lock = 0

spins, testing if Lock = 0

Shared None

2 set lock to 0

(Invalidate received)

(Invalidate received)

Modified (P0) Write invalidate of lock variable from P0

3 Cache miss Cache miss change from Modified to Shared

Bus starts serving P2 cache miss; write back from P0

4 (Wait while bus busy)

Lock = 0 Shared Cache miss for P2 satisfied

5 Lock = 0 Executes exch Shared Cache miss for P1 satisfied

6 Executes exch Completes exch: return 0 and sets Lock = 1

Modified (P2) Write invalidate of lock variable from P2

7 Completes exch: return 1 and sets Lock = 1

Enter critical section

Modified (P1) Write invalidate of lock variable from P1

8 Spins, testing if Lock = 0

None

63

Sincronizzazione tra processi

•  E’ corretto osservare che la tecnica vista funziona senza troppo overhead (ossia, lo spinning non spreca troppi cicli di clock) solo per sistemi con poche CPU, o perlomeno con poche CPU coinvolte nel tentativo di acquisire il lock

•  Quando il numero di CPU coinvolte nel tentativo di acquisire il lock può essere “alto” (diciamo, da 10 CPU in su), vengono usati meccanismi un pò più sofisticati, ma sempre basati sulle primitive di sincronizzazione LL e SC già viste.

22

64

Modelli di consistenza della memoria

•  Garantire la coerenza della cache fa si che le varie CPU di un sistema multiprocessore vedano la memoria principale in maniera consistente fra loro.

•  Tuttavia, la coerenza della cache non risponde alla domanda di cosa debba significare questa consistenza. Ossia: in che ordine una CPU deve/può osservare le modifiche sui dati compiute dalle altre CPU?

•  Infatti, le CPU comunicano attraverso variabili condivise, e una CPU usa delle READ per “osservare” le WRITE di un altra CPU.

•  Quindi, quali proprietà devono valere per le READ e le WRITE eseguite dai processori sulla memoria?

65

Modelli di consistenza della memoria •  Anche nel caso semplice di un sistema NC-NUMA, la presenza di

banchi di memoria locali e remoti alle varie CPU pone un problema di consistenza della memoria.

•  Consideriamo infatti tre CPU che eseguano nell’ordine, in rapida sequenza, le seguenti operazioni sulla cella di memoria X:

–  CPU A: write #1, X

–  CPU B: write #2, X

–  CPU C: read X

•  Che valore legge CPU C? In mancanza di assunzioni a priori, potrà leggere 1, 2 o addirittura il valore precedente alla prima scrittura, a seconda della distanza delle tre CPU rispetto al banco di memoria che contiene la cella indirizzata.

66

Modelli di consistenza della memoria •  In generale, in un dato istante più CPU possono stare cercando di

leggere o scrivere nella stella locazione della memoria vista da tutte le CPU.

•  Nei sistemi DSM le varie richieste di accesso alla locazione possono sorpassarsi l’un l’altra, e quindi essere eseguire in un ordine diverso da quello in cui erano state avviate.

•  Inoltre, ogni CPU è dotata di una (o due, o tre) cache, in cui possono essere presenti più copie della stessa linea di RAM, non necessariamente aggiornate.

•  Il risultato può facilmente essere il caos, a meno che non vengano imposte delle regole di gestione e comportamento della memoria nei confronti dei processori che la condividono.

23

67

Modelli di consistenza della memoria •  Queste regole stabiliscono ciò che viene chiamato un modello di

consistenza della memoria, e diverse regole sono state proposte ed implementate a livello hardware.

•  Alcuni modelli di consistenza sono più rigidi, e quindi più difficili da implementare, mentre altri sono più laschi. In ogni caso, un modello deve permettere di chiarire cosa accade in situazioni come negli esempi precedenti.

•  Solo sulla base in uno specifico modello di consistenza si possono sviluppare le applicazioni che gireranno sul sistema, basandosi proprio su quello specifico modello di funzionamento della memoria.

•  Vediamo alcuni modelli di consistenza tra più diffusi, senza entrare nei dettagli della loro implementazione, che comunque è normalmente realizzata a livello hardware per garantire un sufficiente livello di efficienza.

68

Consistenza stretta CONSISTENZA STRETTA

•  E’ la forma più ovvia di consistenza: qualsiasi lettura ad una cella X restituisce sempre il valore dovuto alla scrittura più recente effettuata nella stessa cella.

•  Sfortunatamente, è anche la forma di consistenza più difficile da garantire: è necessaria un’interfaccia tra le varie CPU e la memoria che gestisca tutte le richieste di accesso alla memoria stessa in modalità first come first served

•  La memoria diviene così un collo di bottiglia che rallenta in modo inaccettabile un sistema costruito per lavorare il più possibile in parallelo

•  Questa forma di consistenza non viene di fatto implementata.

69

Consistenza sequenziale CONSISTENZA SEQUENZIALE

•  Un modello di consistenza più ragionevole assume che, in presenza di una sequenza di più richieste di scrittura e lettura su una certa cella di memoria, un qualche ordine delle richieste viene scelto (eventualmente in maniera non deterministica) dall’ hardware, ma tutte le CPU vedono lo stesso ordine.

•  Come esempio, consideriamo una CPU 1 che scrive 100 in una cella x, e immediatamente dopo una CPU 2 che scrive 200 in x.

•  Dopo la seconda scrittura (non necessariamente già completata), altre due CPU leggono ciascuna due volte la cella x.

24

70

Consistenza sequenziale •  Ecco 3 possibili ordinamenti ((b), (c) e (d)) delle sei operazioni:

sono tutti perfettamente possibili, ma solo il primo, in cui CPU 3 e CPU 4 leggono entrambi (200, 200) rispetta la consistenza sequenziale (Tanenbaum, Fig. 8.22).

71

Consistenza sequenziale

b)  CPU 3 legge (200, 200) – CPU 4 legge (200, 200)

c)  CPU 3 legge (100, 200) – CPU 4 legge (200, 200)

d)  CPU 3 legge (100, 100) – CPU 4 legge (200, 100)

•  Questi, e altri ordinamenti sono possibili, a causa dei diversi possibili ritardi nella propagazione delle operazioni tra le CPU e i moduli di memoria.

•  Ma la consistenza sequenziale non permetterà che le due CPU possano vedere ordini diversi in cui gli eventi si verificano, e quindi solo il caso (b) si sarà permesso da un protocollo hardware/software che implementa la consistenza sequenziale.

72

Consistenza sequenziale •  Di fatto, la consistenza sequenziale permette di asserire che, nel

caso di eventi multipli che avvengono in maniera concorrente, viene scelto un ordine preciso in cui questi eventi si verificano, e quest’ordine è osservato da tutti i processori del sistema (e quindi da tutti i processi del sistema)

•  Sembra una proprietà ovvia, ma in realtà è molto costosa da implementare, e riduce le prestazioni potenziali, soprattutto per sistemi paralleli con molte CPU.

•  Infatti, si deve imporre un ordine in cui le operazioni sulla memoria condivisa vengono eseguite, e nuove operazioni non possono essere avviate fino a che le vecchie non sono state completate.

•  Per questo, sono stati proposti dei modelli di consistenza “relaxed”, più facili da implementare e meno penalizzanti per le prestazioni. Uno di questi modelli è la “consistenza del processore”

25

73

Consistenza del processore

•  E’ una forma di consistenza meno vincolante, ma più facile da implementare, e possiede due proprietà:

1.  Le scritture da parte di una qualsiasi CPU sono viste dalle altre CPU nell’ordine in cui sono state avviate. Se CPU 1 scrive A, B e C in una locazione x, una CPU 2 che legga x in sequenza più volte leggerà prima A, poi B e poi C.

2.  Per ogni locazione di memoria, qualsiasi CPU vede tutte le scritture effettuate da ogni singola CPU in quella locazione nello stesso ordine.

74

Consistenza del processore •  Notate perché abbiamo una consistenza più debole di quella

sequenziale. Supponete che CPU 1 scriva in var A, B e C, mentre CPU 2, concorrentemente con CPU 1, scrive in var X, Y e Z.

•  Secondo la consistenza sequenziale, qualsiasi altra CPU che legga più volte var leggerà una qualche combinazione delle sei scritture, ad esempio X, A, B, Y, C, Z, e questa stessa sequenza verrà letta da ogni CPU nel sistema.

•  Secondo la consistenza del processore, diverse CPU che leggano più volte var potranno leggere sequenze diverse. Ciò che viene garantito è che nessuna CPU vedrà una sequenza in cui, ad esempio, B viene prima di A o Z viene prima di Y. L’ordine in cui ciascuna CPU esegue le sue scritture, viene visto allo stesso modo da tutte le altre.

75

Consistenza del processore •  Quindi, ad esempio, CPU 3 potrà leggere: A, B, C, X, Y, Z. Mentre

CPU 4 potrà leggere X, Y, Z, A, B, C

•  Questo modello di consistenza è adottato da molti sistemi multiprocessore.

26

76

Il Sun Fire E25K •  Un semplice esempio di architettura CC-NUMA è la famiglia Sun

Fire della Sun Microsystems. Il modello E25K è formato da 72 CPU UltraSPARC IV.

•  Una CPU UltraSPARC IV è formata da una coppia di processori UltraSPARC III che condividono cache e memoria

•  Di fatto, come vedremo, il Sun Fire E25K è un sistema combinato UMA / CC-NUMA

•  Il sistema è stato prodotto fino al 2009, sostituito poi dai server Sun Blade, che usano processori multi-core di più recente produzione.

77

Il Sun Fire E25K •  Un sistema E25K consiste in un

massimo di 18 boardset, ognuno dei quali è formato da:

–  una scheda madre CPU-memoria

–  una scheda di input/output con quattro slot PCI (Peripheral Component Interconnect)

–  Una scheda di espansione che accoppia la scheda madre con la scheda di I/O e connette le due schede alla pianta centrale.

•  La pianta centrale ospita le varie schede e contiene i circuiti di switching per la comunicazione fra i vari nodi

78

Il Sun Fire E25K •  Ogni scheda CPU-memoria contiene 4 CPU UltraSPARC IV e 4

moduli da 8 GB di RAM ciascuno, per un totale di 8 CPU UltraSPARC III e 32 GB di RAM

•  Quindi, un sistema che abbia tutti e 18 i boardset consiste di 144 CPU UltraSPARC III, 72 moduli di memoria per un totale di 576 GB di RAM e 72 slot PCI.

•  Ogni UltraSPARC IV ha 64KB + 64 KB di cache L1, 2 MB di cache L2 (on-chip) e 32 MB di cache L3

–  Una nota curiosa: il numero 18 è stato scelto perché un sistema con 18 boardset era il più grande che ancora riusciva a passare attraverso una porta di normale dimensioni senza dover essere smontato.

27

79

Il Sun Fire E25K •  Il punto cruciale dell’architettura è ovviamente la memoria

principale, fisicamente distribuita. Come connettere 144 CPU a 72 moduli di RAM e assicurare la coerenza della cache?

•  Come abbiamo già visto, usare un bus condiviso di snooping quando sono coinvolte molte CPU è poco pratico, perché il bus costituirebbe un collo di bottiglia per le comunicazioni. Analogamente, un crossbar switch 144 x 72 sarebbe troppo difficile e costoso da costruire.

•  Invece, nel Sun Fire la pianta centrale è composta da un insieme di 3 crossbar switch 18 x 18, che mettono in collegamento i 18 boardset.

•  In questo modo, da(lla CPU di) ogni boardset è possibile accedere a(lla memoria di) ogni altro boardset.

80

Il Sun Fire E25K •  Su un crossbar 18 x 18 passano gli indirizzi delle richieste di

accesso alla memoria remota, su un’altro passano le risposte alle richieste, e sul terzo passano i dati veri e propri. L’uso di tre linee separate permette quindi di aumentare il numero di accessi alla memoria remota che possono essere fatti in parallelo.

•  Nella figura, 8 nodi sono connessi ciascuno con tutti gli altri da un crossbar switch 8 x 8. Se fossero disponibili altri due crossbar 8 x 8 ogni nodo potrebbe avere contemporaneamente 3 comunicazioni in corso con altri nodi (H.P3, Fig. 8.13a)

81

Il Sun Fire E25K •  All’interno di ogni boardset, invece, i processori sono (almeno

concettualmente) connessi fra loro da un bus comune su cui è implementata la logica di snooping (Tanenbaum, Fig. 8.32)

28

82

Il Sun Fire E25K •  Proprio a causa di questo sistema di connessione misto, L’E25K

usa, per mantenere la coerenza della cache, un sistema combinato snooping-directory based

•  In ogni boardset viene usata una variante del protocollo di snooping MESI (il MOESI) tra le 8 CPU locali: quando una CPU indirizza un dato che appartiene alla porzione di memoria che sta sullo stesso boardset il protocollo di snooping si occupa di recuperare il dato stesso mantenendo la coerenza di tutte le cache del boardset

•  Se invece viene indirizzata una linea remota rispetto ad una boardset, viene utilizzata una tecnica directory-based, con un’unica directory per boardset che tiene traccia di tutte le linee di quel boardset e ne gestisce le richieste di accesso provenienti da boardset remoti.

83

Il Sun Fire E25K •  Con questa gestione della memoria, il Sun Fire E25K è in grado di

raggiungere prestazioni molto elevate, con un minimo di 40GB che possono essere spostati globalmente nel sistema tra boardset differenti, al secondo (nel caso di un sistema completo, formato da 18 boardset).

•  Tuttavia, se il software è in grado di allocare le pagine tra le varie schede di memoria in modo che la maggior parte degli accessi alla memoria da parte delle varie CPU sono locali, le prestazioni del sistema ne risentono positivamente in modo sensibile.

84

L’SGI ALTIX •  I sistemi SGI ALTIX della SGI (Silicon Graphics Inc.) sono

architetture parallele CC-NUMA fortemente scalabili, sia nel numero di processori che nella quantità di memoria indirizzabile.

•  I sistemi ALTIX sono concepiti principalmente per applicazioni in campo scientifico (e militare) mentre i Sun server (Fire e i più recenti Blade) sono più orientati ad applicazioni commerciali.

•  Sono i successori della serie SGI ORIGIN, che utilizzava processori RISC MIPS-R1000, sostituiti negli ALTIX da ITANIUM 2 e Xeon.

•  Esistono diversi tipi di sistemi, dall’ALTIX 330, che arriva ad un massimo di 16 processori e 128 GB di RAM, all’ALTIX 4700 che arriva fino a 2048 processori Itanium 2 dual core e 32TB di RAM

•  L’ALTIX UV, introdotto a fine 2009, è configurabile da 32 a 2048 core Xeon e fino a 16 TB di RAM

29

85

L’SGI ALTIX •  Un sistema ALTIX è formato da un insieme di nodi, di cui i

principali sono i C-brick, unità computazionali costituite da 4 Itanium 2 connessi a gruppi di due a un massimo di 16+16 GB di RAM.

•  Un Shared-hub (SHUB) connette ogni coppia di processori alla rete di interconnessione, e implementa il protocollo di coerenza della cache.

•  I nodi possono essere costituiti anche solo da banchi di memoria, detti (M-brick), in modo che la quantità di RAM non è vincolata al numero di CPU del sistema.

86

L’SGI ALTIX •  Tra le altre motivazioni, la scelta dell’Itanium 2 è dovuta alla sua

capacità di indirizzare fino a 250 byte di memoria fisica, ossia circa un milione di gigabyte di RAM (mille terabyte di RAM)

•  L’Itanium 2 è in grado di gestire pagine con dimensione da 4KB a 4GB, ed è dotato di 2 TLB da 128 entry per gestire separatamente la traduzione degli indirizzi logici delle istruzioni e dei dati nei corrispondenti indirizzi fisici.

•  E’ quindi possibile (nel caso migliore) indirizzare fino a più di 500 GB di istruzioni e 500 GB di dati senza “TLB page miss”, un evento che è particolarmente pesante da gestire in un sistema a memoria fisica distribuita.

87

L’SGI ALTIX •  Il protocollo di coerenza della cache è implementato dagli SHUB,

che si interfacciano sia con la logica di snooping dei 4 Itanium di un C-brick che con il protocollo directory based usato attraverso la rete di interconnessione.

•  Se la richiesta di accesso ad un dato da parte di una CPU può essere soddisfatta dal contenuto delle cache di una delle altre CPU del nodo, i dati vengono trasmessi direttamente alla cache del processore richiedente senza neanche far arrivare la richiesta alla memoria.

•  Altrimenti, entra in gioco il protocollo directory based, implementato dagli SHUB, ognuno dei quali memorizza la directory della corrispondente coppia di Itanium.

30

88

L’SGI ALTIX •  La gerarchia di cache dell’Itanium: notate i diversi protocolli di

snooping usati per i diversi livelli di cache.

89

L’SGI ALTIX •  I vari nodi sono connessi fra loro in una rete fat tree (detta

NUMAlink 4) a 1600 MB/sec attraverso dei router: gli R-brick

90

Multiprocessori COMA •  In una architettura monoprocessore, così come nelle architetture

multiprocessore a memoria condivisa fin’ora viste, ogni dato, ogni linea, ogni pagina risiedono in un ben determinato punto dello spazio di indirizzamento logico, ed hanno quindi un ben determinato indirizzo (un “home address”).

•  Quando un processore vuole accedere ad un dato, il suo indirizzo logico viene convertito nell’indirizzo fisico, e il contenuto della cella di memoria di RAM che contiene il dato viene copiato nella cache del processore, dove il dato può essere letto e/o modificato.

•  In quest’ultimo caso la copia del dato in RAM dovrà prima o poi essere sovrascritta con la versione aggiornata che sta nella cache del processore che l’ha modificata.

31

91

Multiprocessori COMA •  Questa proprietà (che sembra del tutto ovvia) fa si che nei sistemi

UMA, come nei NUMA, la relazione tra i processori e la memoria sia un punto critico:

•  Nei sistemi NUMA la memoria distribuita può produrre un alto traffico di messaggi per spostare i dati da una CPU all’altra, e per mantenere la coerenza dei valori negli “home address”

•  inoltre, i riferimenti alla memoria remota sono molto più lenti di quelli alla memoria locale. Nei sistemi CC-NUMA questo è in parte nascosto dalla presenza della cache (ma se molte CPU hanno bisogno di molti dati remoti, le prestazioni ne risentono comunque)

•  Nei sistemi UMA la memoria centralizzata produce comunque un collo di bottiglia, e pone dei limiti al grado di connessione tra le CPU e la memoria, e alla loro scalabilità.

92

Multiprocessori COMA •  Proprio per cercare di aggirare questi problemi, nei sistemi COMA

(Cache Only Memory Access) si adotta un principio completamente diverso nella relazione tra i processori e la memoria.

•  Scompare il concetto per cui ogni dato ha un home address, e l’intero spazio di indirizzamento fisico viene visto come un’unica enorme cache

•  I dati possono migrare (quindi spostarsi, e non essere copiati)all’interno dell’intero sistema da un banco all’altro di memoria, su richiesta di una CPU che vuole accedere a quel particolare dato

93

Multiprocessori COMA •  Sperimentalmente, questo modo di gestire la memoria RAM, come

se fosse una enorme cache, aumenta sensibilmente l’hit rate e quindi le prestazioni. Tuttavia, ci sono due problemi di fondo da risolvere:

1.  Quando un indirizzo logico viene tradotto nel corrispondente fisico, e il dato indirizzato non è nella cache o nella RAM locale, dov’è?

2.  Quando un dato A viene portato nella RAM di una CPU può dover sovrascrivere un dato B (perché non c’è più posto disponibile). Che accade se quella era l’ultima copia di B (cosa che di per se può essere difficile da sapere)?

•  Sono state proposte varie soluzioni, basate sull’uso di hardware e protocolli aggiuntivi, ma questo tipo di sistemi e le loro possibili varianti, sono ancora solo in fase di studio.