3. PS3: Cluster o console?
● Specifiche:
– CPU: Processore CELL
– GPU: RSX
– Memoria: 256Mb XDR Ram principale, 256Mb
GDDR3 VRAM
– Hard disk: SATA 2.5'' (dimensioni variabili a
seconda del modello)
– Svariate altre periferiche, a seconda del modello
4. PS3: Cluster o console?
● Dal sito della IBM: ”Cell combines a general-purpose Power
Architecture core of modest performance with streamlined
coprocessing elements which greatly accelerate multimedia and
vector processing applications, as well as many other forms of
dedicated computation.”
● “ Though sold as a game console, what will in fact enter the
home is a Cell-based computer. ” - Ken Kutaragi, padre della
console playstation
5. PS3: Cluster o console?
RISPOSTA: CLUSTER E CONSOLE!
● Accontenta i videogiocatori, fornendo loro
una notevole esperienza videoludica...
● Fornisce un cluster di tutto rispetto per
un prezzo relativamente economico
6. Alcune informazioni
● Cell: abbreviazione di Cell Broadband
Engine Architecture (talvolta si trova anche la
sigla CBEA)
● Progettato in cooperazione da IBM, Sony e
Toshiba espressamente per la PS3
9. Architettura CELL
● 1 Power Processor Element (PPE)
● 8 Synergistic Processor Element (SPE)
● Accesso alla memoria
10. PPE: Power Processor Element
● ”A core to rule them all...”
● Architettura PowerPC a 64 bit 4GHZ (3.2 per la PS3)
● Cache L1: 32KB per le istruzioni, 32KB per i dati
● Cache L2: 512 KB
● Si occupa di mappare i tasks alle SPE
● Su di essa viene eseguito il sistema operativo...
● ... il maggior carico computazionale viene sopportato
dalle SPE
● Dual-threaded
● in-order
11. PPE: Power Processor Element
● Perchè in-order?
– Per poter fornire le capacità di esecuzione out-
of-order, c'è bisogno ovviamente di una
maggiore complessità architetturale
– Lo scheduling delle istruzioni è totale
responsabilità del compilatore...
12. Architettura CELL
● 1 Power Processor Element (PPE)
● 8 Synergistic Processor Element (SPE)
● Accesso alla memoria
14. SPE:Synergistic Processor
Element
● Le SPE rappresentano il ”braccio armato”
della PPE...
● Architettura SIMD
● Si prendono sulle loro spalle il maggior
carico computazionale
● 128 registri a 128 bit
● 4 floating point unit capaci di 32GFLOPS e 4
integer unit capaci di 32 GOPS
● Local store di 256KB... Manca qualcosa?
15. SPE:Synergistic Processor
Element
● Eh? manca la cache!
● Motivazioni di questa scelta:
– Aggiunge complicazioni, dovute alla natura
stessa della cache
– Possiamo usare il Local Store...
16. SPE:Synergistic Processor
Element
● LOCAL STORE
– Tramite questo la SPE accede alla memoria
(non può farlo direttamente)
– Possiamo spostare 16byte(128 bit) per ciclo da e
verso la local store... esattamente il carico di
ogni registro!
– A differenza della cache, la local store può
sostenere questo carico per oltre 10000 cicli
17. SPE:Synergistic Processor
Element
● Un possibile problema: contesa
– Dobbiamo contemporaneamente caricare dati
dalla memoria, scrivere dati in memoria e
scrivere/caricare dati nei/dai registri...
● Soluzione:
– La local store accede alla memoria in blocchi di
almeno 1024bit per ciclo (circa 0.5 tera al
secondo!)
18. SPE:Synergistic Processor
Element
● Local Store vs CACHE
– Un tipico esempio: audio processing
● Se riesco a caricare all'interno del Local Store l'intero
blocco di audio su cui devo lavorare, annullo gli
accessi alla memoria durante l'esecuzione del
programma...
● Impossibile (o comunque estremamente difficile) farlo
con la cache
20. Pensandoci su...
● Perchè tutto questo funzioni, c'è bisogno che
le SPU siano sempre riempite di dati...
● Necessità di un sistema di accesso alla
memoria e alle periferiche di I/O altamente
performante...
21. Architettura CELL
● 1 Power Processor Element (PPE)
● 8 Synergistic Processor Element (SPE)
● Accesso alla memoria
– EIB e MMU
– Memorie e dispositivi di I/O
22. Element Interconnect Bus
● Unisce tutte le componenti sul chip
● Composto di 4 anelli da 16byte ciascuno e
permettono di effettuare fino a 3 trasferimenti
simultanei.
● Cosa fa?
– Si occupa di spostare dati dalla memoria
principale alle SPE e viceversa
– Smista le istruzioni della PPE alle varie SPE
23. Element Interconnect Bus
● Prestazioni:
– 96 bytes per ciclo di clock, anche se secondo
l'IBM ne sarebbero disponibili solo 64 nella
pratica
● E la protezione della memoria?
– Gestita dalle MMU contenute nelle SPE
24. Memory Management Unit
● Utilizzate dalle SPE per l'accesso alla
memoria o per accedere agli altri Local Store
● Forniscono i meccanismi di coerenza e
protezione della memoria
25. Importanza di EIB e MMU
● Entrambi devono essere performanti...
● Ma sufficientemente complessi da riuscire a
gestire un enorme flusso di dati da distribuire
in diverse destinazioni e una coda di almeno
128 richieste di memoria
26. Architettura CELL
● 1 Power Processor Element (PPE)
● 8 Synergistic Processor Element (SPE)
● Accesso alla memoria
– EIB e MMU
– Memorie e dispositivi di I/O
27. Memorie e dispositivi di I/O
● Dati tecnici:
– XDR Rambus 256MB
– Controller FlexIO
– Ampiezza di banda della memoria: 25.6 GB/sec
– Ampiezza di banda dei dispositivi di I/O: 76.8
GB/sec
28. Intervallo
● Dolorosi incidenti di percorso... (vedi
anche: ”la zappa sui piedi”)
– come ho distrutto la playstation (1)
– come ho distrutto la playstation (2)
29. Come ho distrutto la playstation
(1)
● Configurazione del sistema: Yellow Dog Linux 5.0
● Necessario installare il cellsdk, ma a ogni tentativo
ricevo un errore ”can't update glibc because of
unresolved dependencies”
● IDEA: rimuovo le dipendenze, tanto ”se tolgo
qualcosa di importante il sistema mi avviserà...”
DON
30. Come ho distrutto la playstation
(1)
● A fianco vedete il
risultato...
● Soluzione:
formattazione e
installazione di
Fedora 5
31. Come ho distrutto la playstation
(2)
● Installata fedora 5, ma il cellsdk3.0 richiede fedora
7!
● Piano piano, aggiorno glibc dalla versione della 5
alla versione della 7...
● installo il cellsdk...
● applico varie patch al kernel...
● compilo ed eseguo con successo vari programmi...
● il tutto senza MAI riavviare la playstation 3!
DON
32. Come ho distrutto la playstation
(2)
● La mia espressione
al successivo riavvio
della playstation
● Soluzione: installata
fedora 7 e il cellsdk
seguendo le
”procedure
standard”
34. Una citazione doverosa...
“To put it quite bluntly: as long as there were no
machines, programming was no problem at all;
when we had a few weak computers,
programming became a mild problem, and now
we have gigantic computers, programming has
become an equally gigantic problem."
-- E. Dijkstra, 1972 Turing Award Lecture
Sarà vero??
35. Programmare con il Cell
● Ingredienti:
– Conoscerne l'architettura
– Tanta pazienza
– Linux (Fedora 7.0 o Red Hat Enterprise 5.0)
– IBM CellSDK 3.0
36. CellSDK
● Scaricabile dal sito della IBM (www.ibm.com)
● Fornisce un ambiente di sviluppo per la
programmazione con il Cell, compatibile con
qualsiasi architettura (purchè abbia FC 7 o
RHE 5 installato)
37. CellSDK
● STRUMENTI DI SVILUPPO
– libspe.h, libspe2.h
– ppu-gcc
– spu-gcc
– ppu-embedspu
● STRUMENTI DI DEBUGGING
– ppu-gdb
– spu-gdb
● ALTRO
– systemsim-cell
38. Altri tools utili non inclusi
nell'sdk
● Eclipse + plugin per il cellsdk (richiede la
JVM della IBM)
● Anjuta o Kdevelop
39. Hello, Cell!
● Obiettivo: scrivere il classico ”Hello, world”
utilizzando tutti le SPE del cell.
1) Isolare il task che ogni SPE dovrà eseguire (in
questo caso stampare la stringa ”Hello, world da
spe_id” (dove spe_id è l'id della SPE in
esadecimale)
2) Fare in modo che il PPE mappi il task giusto ad
ogni SPE
40. Hello, Cell!
● Il primo passo è abbastanza banale...
#include <stdio.h>
int main(unsigned long long spe_id, unsigned long long argp,
unsigned long long envp){
printf(”Hello, world! da 0x%xn”,(unsigned int) spe_id);
return 0;
}
#include <stdio.h>
int main(unsigned long long spe_id, unsigned long long argp,
unsigned long long envp){
printf(”Hello, world! da 0x%xn”,(unsigned int) spe_id);
return 0;
}
41. Hello, Cell!
● Passo 2: iniziano i guai...
– come indichiamo alla PPE il programma da
includere?
– come effettuiamo il mapping ai vari SPE?
42. Hello, Cell!
● I programmi che vanno eseguiti dalle SPE
sono scritti per lavorare insieme alla PPE,
ma hanno vengono compilati
indipendentemente
● 1 compilatore per i programmi della PPE e 1
per i programmi della SPE, rispettivamente
ppu-gcc e spu-gcc
43. Hello, Cell!
● spu-gcc produce delle immagini ELF
(Extensible Linking Format) che possono
essere incluse nei programmi della PPE
● Ogni immagine ha la propria sezione di dati
e di codice
● Le funzioni di Input/Output vengono eseguite
richiedendo il servizio alla PPE
44. Hello, Cell!
extern spe_program_handle
spe_program;
extern spe_program_handle
spe_program;
Ecco come dichiareremo il programma ”esterno” da
inserire nelle SPE all'interno della PPE
Ora... bisogna dire alle SPE di caricare questo
programma!
45. Hello, Cell!
● Soluzione: potremmo dire a ogni PPE di
eseguire un thread corrispondente al
programma sopra descritto
● In libspe.h, esistono funzioni apposite...
– spe_create_thread() - crea un thread che
esegue al suo interno il programma
– spe_wait() - aspetta la terminazione di un thread
46. Hello, Cell!
#include <stdio.h>
#include <libspe2.h>
#define NUM_THREAD 6
extern spe_program_handle spe_program;
int main(void){
spe_id_t[NUM_THREAD] spe_id;
int i;
//creiamo i thread...
for(i=0;i<NUM_THREAD;i++){
id[i] = spe_create_thread(0,&spe_program,NULL,NULL,-1,0);
}
//attendiamo la loro terminazione
for(i=0;i<NUM_THREAD;i++){
spe_wait(id[i],NULL,0);
}
}
#include <stdio.h>
#include <libspe2.h>
#define NUM_THREAD 6
extern spe_program_handle spe_program;
int main(void){
spe_id_t[NUM_THREAD] spe_id;
int i;
//creiamo i thread...
for(i=0;i<NUM_THREAD;i++){
id[i] = spe_create_thread(0,&spe_program,NULL,NULL,-1,0);
}
//attendiamo la loro terminazione
for(i=0;i<NUM_THREAD;i++){
spe_wait(id[i],NULL,0);
}
}
49. Hello, Cell!
● Riscriviamo il programma usando la
libspe2...
● A livello concettuale, vale tutto quello che
abbiamo detto finora...
● semplicemente, siamo costretti a usare la
pthread per la gestione dei threads
50. Hello, Cell!
● Creazione/distruzione dei thread
– pthread_create(id,thread_attr,thread_func,func_arg);
– pthread_join(id,value);
● Più alcuni amici da libspe2...
– spe_context_create(flags,gang_context)
– spe_program_load(spe_context_ptr,program_handle
)
– spe_context_run(spe_context_ptr,entry_point,runflag
s,argp,envp,stopinfo)
– spe_context_destroy(spe_context_ptr)
51. Amici da libspe2
● spe_context_create(flags,gang_context)
– flags:
● SPE_EVENTS_ENABLE: abilita la gestione degli
eventi
●
– gang_context:
● equivalente al thread group, viene creato usando
spe_gang_context_create
Crea il context in cui viene eseguito il programma esterno
52. Amici da libspe2
● spe_program_load(spe_context_ptr,progr
am_handle)
– spe_context_ptr: puntatore al context spe
– program_handle: puntatore al programma da
eseguire all'interno del context specificato
Carica il programma all'interno del context specificato
53. Amici da libspe2
● spe_context_run(spe_context_ptr,entry_p
oint,runflags,argp,envp,stopinfo)
– spe_context_ptr: puntatore allo spe_context da
eseguire
– entry_point: parametro inout
● in: il valore dell'instruction pointer da cui il programma
inizierà la sua esecuzione
● out: il valore dell'instruction pointer al momento della
terminazione del programma
54. Amici da libspe2
● spe_context_run(spe_context_ptr,entry_p
oint,runflags,argp,envp,stopinfo)
– runflags: or bitwise di più costanti
● 0: comportamento di default
● SPE_NO_CALLBACKS: vieta l'esecuzione
automatica di callbacks
● SPE_RUN_USER_REGS: inizializza i 3 registri della
SPU con i 48bytes puntati da argp
– argp: (OPZIONALE) puntatore a dati del
programma passati come secondo argomento
55. Amici da libspe2
● spe_context_run(spe_context_ptr,entry_p
oint,runflags,argp,envp,stopinfo)
– envp: (OPZIONALE) puntatore a dati specifici
dell'ambiente di esecuzione, passati come terzo
parametro al programma
– stopinfo: (OPZIONALE) puntatore a una struttura
spe_stop_info_t in cui scriveremo i dati relativi
alla terminazione del programma
56. Amici da libspe2
● spe_context_destroy(spe_context_ptr)
– libera le risorse associate a uno spe_context
57. Allora, programmiamo?
typedef struct{
spe_context_ptr_t spe_id;
unsigned long *args;
} thread_arg_t; //wrapping degli argomenti della funzione
void *run_program(void* thread_arg){
unsigned int entry;
entry = SPE_DEFAULT_ENTRY;
spe_stop_info_t stop_info;
ret = spe_context_run(arg->spe,&entry,0,NULL,NULL,&stop_info);
if(ret<0){
perror("Errore di context_run");
return NULL;
}
printf("status %dn",spe_stop_info_read(arg->spe,&stop_info));
return NULL;
}
typedef struct{
spe_context_ptr_t spe_id;
unsigned long *args;
} thread_arg_t; //wrapping degli argomenti della funzione
void *run_program(void* thread_arg){
unsigned int entry;
entry = SPE_DEFAULT_ENTRY;
spe_stop_info_t stop_info;
ret = spe_context_run(arg->spe,&entry,0,NULL,NULL,&stop_info);
if(ret<0){
perror("Errore di context_run");
return NULL;
}
printf("status %dn",spe_stop_info_read(arg->spe,&stop_info));
return NULL;
}
58. Allora, programmiamo?
int main(int argc,char **argv){
int i,ret_value;
spe_context_ptr_t spe[NUM_THREADS];
pthread_t thread[NUM_THREADS];
thread_arg_t arg[NUM_THREADS];
for(i=0;i<NUM_THREADS;i++){
spe[i] = spe_context_create(SPE_EVENTS_ENABLE,NULL);
if(!spe[i]){
perror("spe_context_create error n");
exit(1);
}
ret = spe_program_load(spe[i],&hello_spu);
if(ret){
perror("errore di spe_program_loadn");
exit(1);
}//... CONTINUA
int main(int argc,char **argv){
int i,ret_value;
spe_context_ptr_t spe[NUM_THREADS];
pthread_t thread[NUM_THREADS];
thread_arg_t arg[NUM_THREADS];
for(i=0;i<NUM_THREADS;i++){
spe[i] = spe_context_create(SPE_EVENTS_ENABLE,NULL);
if(!spe[i]){
perror("spe_context_create error n");
exit(1);
}
ret = spe_program_load(spe[i],&hello_spu);
if(ret){
perror("errore di spe_program_loadn");
exit(1);
}//... CONTINUA
59. Allora, programmiamo?
arg[i].spe = spe[i];
arg[i].args = NULL;
ret = pthread_create(&thread[i],NULL,run_spu_program,&arg[i]);
if(ret){
perror("errore di pthread_createn");
exit(1);
}
}
for(i=0;i<NUM_THREADS;i++){
pthread_join(thread[i],NULL);
ret = spe_context_destroy(spe[i]);
if(ret){
perror("errore di spe_context_destroy!n");
exit(1);
}
}
return 0;
}
arg[i].spe = spe[i];
arg[i].args = NULL;
ret = pthread_create(&thread[i],NULL,run_spu_program,&arg[i]);
if(ret){
perror("errore di pthread_createn");
exit(1);
}
}
for(i=0;i<NUM_THREADS;i++){
pthread_join(thread[i],NULL);
ret = spe_context_destroy(spe[i]);
if(ret){
perror("errore di spe_context_destroy!n");
exit(1);
}
}
return 0;
}
61. Funzioni di accesso alla
memoria
● int spe_mfcio_put (spe_context_ptr_t spe,
unsigned int lsa, void *ea, unsigned int size,
unsigned int tag, unsigned int tid, unsigned
int rid)
● int spe_mfcio_get (spe_context_ptr_t spe,
unsigned int lsa, void *ea, unsigned int size,
unsigned int tag, unsigned int tid, unsigned
int rid)
62. Comunicazione tra 2 SPE
● int spe_out_mbox_read (spe_context_ptr_t
spe, unsigned int *mbox_data, int count)
● int spe_in_mbox_write (spe_context_ptr_t
spe, unsigned int *mbox_data, int count,
unsigned int behavior)