Download - Cell Programming 2
Processore CELL: Memoria, IPC e benchmarking
Vincenzo De Maiomatricola 0510200251
Nella precedente puntata: Architettura del processore Cell
Introduzione al CellSDK 3.0
Sviluppo del programma “Hello, World!” sul processore Cell
BONUS TRACK:
Come distruggere la playstation in maniera efficiente
Sommario
Accesso alla memoria
Comunicazione inter-processore
Benchmarking
Interfacce di accesso alla memoria
Channel Interface (SPU)
− Accesso LOCALE, a bassissima latenza (6 cicli di clock in caso di accesso non bloccante)
MMIO (Memory Mapped I/O) Interface (PPE, altre SPE)
− Accesso a tutte le MFC, mappando gli indirizzi locali in indirizzi validi nell'intero spazio di sistema
Accesso alla memoria Problema 1: Diversi domini di indirizzi
− Un indirizzo di memoria della PPE non ha senso nelle SPE!
Problema 2: Limitazioni di accesso
− Le SPU possono accedere solo ai dati presenti nel proprio Local Store
Un “Ripasso”: Il MFC (Memory Flow Controller)
Lo “special guest” della giornata...
Rappresenta l'interfaccia che mette in comunicazione la SPU con l'EIB (e di conseguenza con la memoria centrale, gli altri elementi del processore e i dispositivi di I/O)
Gestisce la comunicazione interprocessore (mailbox, segnali, interrupt...) e ha al suo interno un controller DMA
Domini di indirizzi
1 Dominio della memoria principale (Real Address Space)
6 Domini di Local Store (1 per ogni SPE) (Local Store Address Space)
Effective Address Space
Limitazioni di accesso
La PPE può accedere facilmente alle Local Store, viceversa le SPE effettuano SOLO accessi locali...
Come avviene l'invio di dati alla SPE?
Invio di dati alla SPE: l'idea
Tramite il DMA, posso tradurre l'indirizzo nel contesto della PPE in un indirizzo “effettivo” ed effettuare una copia di questi dati nella Local Store
− NOTA: La memoria della Local Store è limitata a 256 KB...
attenzione a non riempirla! Una volta effettuate le operazioni necessarie, è
necessario effettuare una scrittura nel dominio della memoria centrale
Comandi DMA
Interfaccia di accesso della PPE e della SPE
Comando FUNZIONEPPE SPE
GETGETFGETBPUTPUTFPUTB
spe_mfcio_get mfc_get
spe_mfcio_getf mfc_getf
spe_mfcio_getb mfc_getb
spe_mfcio_put mfc_put
spe_mfcio_putf mfc_putf
spe_mfcio_putb mfc_putb
Comandi DMA (SPE) Parametri di un comando DMA
− void* lsa: indirizzo della local store (in cui scrivere/leggere)
− uint64_t ea: l'indirizzo effettivo (in cui scrivere/leggere)
− uint32_t size: il numero di byte da scrivere/leggere
− uint32_t tag: il tag di un gruppo di comandi DMA
− uint32_t tid, uint32_t rid: transfer class id, replacement class id (solitamente settati a 0)
Altre funzioni necessarie uint32_t mfc_tag_reserve(), per ottenere un tag valido
uint32_t mfc_multi_tag_reserve(uint32_t n), per ottenere una serie di n tag contigui (restituisce il primo della serie)
mfc_write_tag_mask(uint32_t tag), indica il tag da attendere
mfc_read_tag_status_*(), attende la terminazione dei tag nella tag mask
Tutte queste funzioni, comprese mfc_get e mfc_put sono nella libreria spu_mfcio.h
Un po' di astrazione...
#define wait_all_tag(tag) mfc_write_tag_mask(1<<tag); mfc_read_tag_status_all()#define wait_any_tag(tag) mfc_write_tag_mask(1<<tag); mfc_read_tag_status_any();
mfc_write(volatile void* dest,unsigned int src, unsigned int tag, int size){mfc_put(dest,src,size,tag,0,0);wait_all_tag(tag);
}
mfc_read(volatile void* src,unsigned int dest,unsigned int tag,int size){mfc_get(src,dest,size,tag,0,0);wait_all_tag(tag);
}
Un po' di codice (PPE->SPE) Dopo ben 12 slide al riguardo, ne sappiamo abbastanza per implementare un semplice programma che si occupi di inviare/ricevere dati a/da una SPE...
La PPE invia un vettore e un valore numerico alla SPE; la SPE aggiunge a ogni elemento del vettore il valore
Shopping list:
− Invio di dati a una SPE (argp)
− Utilizzo dei comandi DMA nel contesto SPE
− BONUS: Aggiunta del valore in modalità SIMD
Dati da inviare
#ifndef __control_block_h__#define __control_block_h__
typedef struct _control_block { unsigned int shift; //valore da aggiungere unsigned int addr; //indirizzo char pad[120]; //padding} control_block;
#endif
Dichiarazione e invio del vettore numerico (PPE)
#include <libspe2.h>#include <malloc_align.h>#define BUFF_SIZE 64#define BUFF_DIM BUFF_SIZE * sizeof(int)#define NUM_SPE 1control_block cb __attribute__ ((aligned (128))); //struct da inviareint *arr; //array da inizializzare
int main(){int i;arr = (int*)_malloc_align(BUFF_DIM,7);cb.shift = 3;cb.addr = (unsigned int) arr;/* omesse dichiarazioni dei contesti, delle strutture necessarie ai
thread e del ciclo in cui i thread vengono avviati*/arg[i].argp = &cb;
}
Ricezione del vettore (SPE)volatile control_block cb __attribute__ ((aligned (128)));int arr[BUFF_SIZE] __attribute__ ((aligned (128)));
int main(unsigned long long speid, unsigned long long argp, unsigned long long envp){
int i;unsigned int tag_id;if((tag_id = mfc_tag_reserve())==MFC_TAG_INVALID){
printf("Impossibile riservare il tag!\n");return 1;
}mfc_read(&cb,(unsigned int)argp,1,sizeof(cb));mfc_read(arr,(unsigned int)cb.addr,tag_id,sizeof(arr));vector_increment(arr,BUFF_SIZE,SIMD_MODE,cb.shift);mfc_write(arr,(unsigned int*)cb.addr,tag_id,sizeof(arr));return 0;
}
Di nuovo alla PPE...
//attendiamo la terminazione dei thread...for(i=0;i<NUM_THREADS;i++){
pthread_join(thread[i],NULL);destroy_spe(spe[i]);
}//stampo i risultatifor(i=0;i<BUFF_SIZE;i++) printf("%d ",arr[i]);
Invio di dati, PPE<-SPE Adesso vedremo come ricevere dati dalla SPE
nella PPE...
Un semplice programma che legge una stringa salvata in una SPE
La SPE invia alla PPE un indirizzo da cui leggere
Shopping list:
− Ricezione di un indirizzo da una SPE − Scrittura di un dato leggendo dall'indirizzo
(spe_mfcio_*)
Codice PPE#include <libspe2.h>
uint32_t ls_offset; // spiazzamento dei dati nella Local Storevolatile char my_data[BUFF_SIZE] __attribute__ ((aligned(128))); //buffer dei datiint main(int argc, char *argv[]){ int ret; uint32_t tag, status; /* Omessa creazione dei thread e assegnamento del tag*/
do{
ret=spe_mfcio_put( spe_ctx, ls_offset, (void*)my_data, BUFF_SIZE, tag, 0,0);
}while( ret!=0);
ret = spe_mfcio_tag_status_read(spe_ctx,0,SPE_TAG_ALL, &status);__lwsync();
}
Alcune considerazioni
Il modello di accesso alla memoria, pur essendo a basso livello, è abbastanza “pulito”...
Molto performante, adatto al trattamento di array, puntatori e grosse quantità di dati...
Ma se volessi inviare un semplice intero?
DMA vs IPC
In caso di invio di semplici interi a 32 bit, questo approccio è perdente...
Inutile inviare 128 byte per riceverne 4... Meglio utilizzare qualcosa studiato appositamente per questi casi
Sommario
Accesso alla memoria
Comunicazione inter-processore
− Mailbox
− Segnali
Benchmarking
Mailbox
Un semplice meccanismo di comunicazione interprocessore, studiato per l'invio di messaggi a 32 bit
Altamente performante, specie per le SPE... ☺
Rischia però di sovraccaricare l'EIB, in caso di polling... quindi attenzione!
Mailbox
Per SPE abbiamo:
− 2 outbound mailbox (per le interrupt e per la comunicazione con la PPE e altre SPE) (1 entry)
− 1 inbound mailbox (per la ricezione di dati dalla PPE o da altre SPE) (4 entries)
Per ogni mailbox abbiamo
− Counter: il numero di entries presenti
− Le mailbox sono implementate come code FIFO
Differenze tra le mailboxes
Comportamento
InboundOutbound
Counter
Decrementato quando un messaggio viene letto, incrementato quando un messaggio viene scritto
Incrementato quando la spu scrive un messaggio, decrementato quando un messaggio viene letto
Overrun
Quando la PPE prova a scrivere e la fifo è piena, viene sovrascritta l'ultima entry
Quando la SPU legge da un buffer vuoto, resta bloccata in attesa di dati
La SPU si blocca nel caso tenta di leggere un buffer vuoto, mentre la PPE non si blocca mai
Quando la SPU scrive in un buffer pieno resta bloccata, invece la PPE si preoccupa solo di restituire un valore errato
API per l'utilizzo delle mailboxes
PPE (MMIO Interface)
− spe_out_mbox_read(spe_context_ptr_t spe,unsigned int *mbox_data,int count, unsigned int behavior)
− spe_in_mbox_write(spe_context_ptr_t spe, unsigned int *mbox,int count,unsigned int behavior)
− spe_in_mbox_status(spe_context_ptr_t spe)
− spe_out_mbox_status(spe_context_ptr_t spe)
API per l'utilizzo delle mailboxes
SPE (Channel Interface)
− spu_write_out_mbox(uint32_t data)
− spu_read_in_mbox()
− spu_stat_in_mbox()
− spu_stat_out_mbox()
Echo, questo sconosciuto...
SPE
uint32_t data;while(spu_stat_in_mbox()<entries){}data = spu_in_mbox_read();spu_write_out_mbox(data)
PPEunsigned int data = 32,recvd;while(spe_in_mbox_status(spe[0])<1){ //wait }spe_write_in_mbox(spe[0],data,1,SPE_MBOX_ALL_BLOCKING);.........spe_read_out_mbox(spe[0],&recvd,1);
??? manca qualcosa!!
Manca l'invio di messaggi diretto tra le SPE!
Con queste funzioni non possiamo inviare dati senza passare per la PPE...
IDEA: conoscendo l'indirizzo di memoria della mailbox, si potrebbero utilizzare le funzioni MFC...
Implementazione della comunicazione SPE-SPE con
le mailbox Per ottenere l'accesso a determinate aree della
SPU, nella funzione libspe2.h abbiamo una funzione apposita:
volatile spe_spu_control_area_t* ctl_area;ctl_area = (spe_spu_control_area_t)* spe_ps_area_get(spe[i],SPE_CONTROL_AREA);uint64_t ctl_addr;ctl_addr = (uint64_t)ctl_area;while(spe_in_mbox_status(spe[i])<4){}// invio dell'indirizzo alla SPEspe_in_mbox_write(spe[i],(uint32_t*)&ctl_addr,2,SPE_MBOX_ALL_BLOCKING);
Ricezione dell'indirizzo
uint32_t ea_h,ea_l;uint64_t eff_addr;while(spu_stat_in_mbox()<2){}ea_h=spu_read_in_mbox(); //32 bit più significativiea_l=spu_read_in_mbox(); //32 bit meno significativieff_addr =mfc_hl2ea(ea_h,ea_l); //concatenazione
Scrittura in un'altra SPE (1)#define SPU_IN_MBOX_OFFSET 0x0C #define SPU_IN_MBOX_OFFSET_SLOT 0x3#define SPU_MBOX_STAT_OFFSET 0x14#define SPU_MBOX_STAT_OFFSET_SLOT 0x1 //alcuni dati di utilità
inline int status_at_mbox(uint64_t address,uint32_t tag){uint32_t status[4],id;uint64_t ea_stat_mbox = address + SPU_MBOX_STAT_OFFSET;id = SPU_MBOX_STAT_OFFSET_SLOT;mfc_get((void*)&status[id],ea_stat_mbox,sizeof(uint32_t),tag,0,0);mfc_write_tag_mask(1<<tag);mfc_read_tag_status_any();return status[id];
}
inline int status_at_in_mbox(uint64_t address,uint32_t tag){int status;status = status_at_mbox(address,tag);status = (status&0x0000ff00)>>8;return status;
}
Scrittura in un'altra SPE (2)
inline int write_in_mbox(uint32_t data,uint64_t ea,uint32_t tag){uint64_t ea_mailbox = ea + SPU_IN_MBOX_OFFSET;uint32_t mbox[4],id;int status;while((status=status_at_in_mbox(ea,tag))<1);id = SPU_IN_MBOX_OFFSET_SLOT;mbox[id] = data;mfc_put((void*)&mbox[id],ea_mailbox,sizeof(uint32_t),tag,0,0
);mfc_write_tag_mask(1<<tag);mfc_read_tag_status_any();return 1;
}
Sommario
Accesso alla memoria
Comunicazione inter-processore
− Mailbox
− Segnali
Benchmarking
Segnali
A differenza dei segnali UNIX, nella CBEA i segnali implementano un meccanismo molto simile alle mailbox...
Per la gestione asincrona di eventi esiste una gestione simile a UNIX...
Ma non la vedremo in questo seminario!
Segnali
Ogni SPE ha 2 registri a 32 bit per la segnalazione, assolutamente identici
Consente l'invio di interi a 32 bit
La PPU effettua/riceve segnalazioni tramite la MMIO interface, mentre la SPU utilizza la Channel Interface per leggere i suoi registri
Segnali vs Mailbox I segnali, a differenza delle mailbox, non hanno
“entries”...
Sono UNIDIREZIONALI
Consentono due diverse modalità di scrittura
− OR mode: le write vengono combinate attraverso un'operazione di or bit a bit
− Overwrite mode: successive write sovrascrivono il valore presente nel registro
Una lettura del counter restituisce solo 0, se non ci sono segnali pendenti, o 1 se ce n'è almeno uno.
API per l'utilizzo dei segnali
PPE
− spe_signal_write(spe_context_ptr_t spe,unsigned int notification_registry,unsigned int data)
− spe_context_ptr_t spe_context_create(unsigned int flags, spe_gang_context_ptr_t gang) (per utilizzare la modalità OR, bisogna passare come flag SPE_CFG_SIGNOTIFY_OR*
SPE
− uint32_t spu_read_signal*()
Segnali SPE<->SPE
È possibile sfruttare lo stesso principio pensato per le mailbox per implementare la segnalazione tra 2 SPE...
Tuttavia per brevità non la vedremo in questo seminario!
Intervallo: I consigli della nonna
Ascoltatemi, io c' ho esperienza
Il soggetto della foto è maggiorenne e consenziente al trattamento dei dati personali ai sensi della legge.
I consigli della nonna Delegare quanto più possibile il lavoro alle SPE
Sfruttare il parallelismo
− A task separati corrispondono SPE separate
− Il numero dei thread non deve MAI superare il numero delle SPE
− Non abusare dei threads, in quanto la loro creazione sovraccarica il sistema
Utilizzare la precisione doppia SOLO se necessario
Cercare di ricorrere alle istruzioni di sincronizzazione il meno possibile
Utilizzare la keyword volatile, al fine di indicare al compilatore di non riordinare gli accessi di memoria ai buffer dichiarati in questo modo
Sommario
Accesso alla memoria
Comunicazione inter-processore
− Mailbox
− Segnali
Benchmarking
Tutto questo a che pro?
Lo scopo ultimo del mio lavoro è il porting su CELL di un programma di dinamica molecolare
Una semplice applicazione parallela, secondo il paradigma SCATTER-PROCESS-GATHER
The making of...
Il programma originario è scritto in FORTRAN77
PRIMO PROBLEMA: Riutilizzare le funzioni FORTRAN in un programma scritto in C
Riutilizzo delle subroutines FORTRAN
Possibile?
− Si, da qualche parte tutto diventa assembly :)
− Basta compilare separatamente il file oggetto (maggiori dettagli in seguito)
C'è solo bisogno di sapere alcune cose...
− Le funzioni C accettano il passaggio di parametri per valore e per riferimento, mentre quelle fortran SOLO per riferimento...
− In FORTRAN77 non esiste allocazione dinamica (ne' tantomeno i puntatori!)
Riutilizzo delle subroutines FORTRAN
FORTRAN77a = 5b = 3subroutine add(a,b)
a+breturn
end
C che richiama FORTRAN77
int a=5,b=3;add_(&a,&b);
Riutilizzo delle subroutines FORTRAN
Cint a=5,b=3;void add_(int *sum,int *a,int *b){
*sum = *a + *b;}
FORTRAN77 che richiama C
call add(sum,a,b)
E le variabili?
Per comodità, nel programma FORTRAN77 tutte le variabili sono dichiarati all'interno di common blocks (gli antenati delle struct...) in un file .h
Equivalenti a una extern struct in C
Riutilizzo dei COMMON BLOCKS
FORTRAN
real*4 alat,dt,dtforce,sigma,sigsq,cutsq1,cutsq2common /blk01/ alat,dt,dtforce,sigma,sigsq,cutsq1,cutsq2
C che richiama FORTRAN
extern struct blk01_;float alat = blk01_.alat;
Tutto finito?
Magari...
Nel FORTRAN77 non esiste allocazione dinamica, quindi tutto viene preallocato staticamente...
La SPE ha un limite di memoria di 256 KB, per dati e istruzioni!
Secondo voi è sufficiente?
Dimensione dati
Un breve calcolo...
− 60 byte(per atomo) * 10000 (numero di atomi preallocati) = 600000 byte = 585.9375KB
− 62 float = 62 * 4 byte = 248 byte
− Vari altri parametri utili al programma...
− Devo continuare?
Allocazione dinamica
In FORTRAN?
− Non esiste in FORTRAN77, ma è stata implementata nelle versioni successive del linguaggio e il compilatore spu-gfortran accetta codice da FORTRAN77 a Fortran95
− Non può essere usata per i common blocks
Meglio in C...
− Elevata esperienza d'uso e facilità di gestione
Allocazione Dinamica
Soluzione prescelta:
− Variabili dichiarate in C, così come i puntatori da allocare utilizzando la malloc...
− In seguito, effettuo il passaggio di array e variabili alla funzione FORTRAN da utilizzare, dopo aver modificato la stessa in modo che accetti tutti i valori in input...
Come agire sul codiceFORTRAN INIZIALEinteger arr(30)subroutine add_arr(a,b)
do i=1,30 arr(i) = a+b;enddoreturn
end
FORTRAN MODIFICATOsubroutine add(a,b,arr,dim_arr)
integer,intent(in)::dim_arrinteger,intent(inout),dimension(dim_arr)::arrdo i=1,30 arr(i) = a+b;enddo
end
Tuttavia...
Nonostante questo, il programma era ancora troppo grande per le SPE...
Elimino le funzioni di input/output mappando questa fase sulla PPE
Ancora troppo grande!
Soluzione definitiva
Dopo averle pensate praticamente TUTTE, compreso spu-strip e spu-readelf...
Commento una write() per effettuare un test e...
IL PROGRAMMA ENTRA NELLA SPE!
Mistero!
Il compilatore spu-gfortran, appena trovava quella write, includeva staticamente l'intera libreria di input/output di FORTRAN77...
Trasformando un programma di 160KB in un programma di 660KB!
Morale della favola:
− Per le stampe a schermo necessarie alla gestione degli errori, meglio richiamare una funzione scritta in C dal programma FORTRAN77.
Il mio lavoro finora...
Suddivisione del lavoro in un'immagine SPE e un'immagine PPE
Nell'immagine PPE effettuo l'input dei dati necessari alla computazione, invio i dati alla SPE e ne attendo la terminazione
Nell'immagine SPE inizializzo la porzione di array assegnata ed effettuo il processing basandomi SOLO sui dati locali
Cosa manca?
La comunicazione tra le varie SPE
Il gathering dei dati
Una migliore gestione della memoria
− Il programma riesce a gestire fino a poco più di 200 atomi per SPE... :'(
Risultati ottenuti finora
Abbiamo effettuato un test basandoci su questa porzione di lavoro e confrontandola con il programma originario “Modificato”
Non molto attendibile, visto che il maggiore overhead è rappresentato proprio dalla comunicazione e dal gathering...
Benchmarking
Conclusioni (per ora)
Non sono dati definitivi, ma considerando che
− Il programma CELL non ha ancora ottimizzazioni di sorta
− Non utilizzo tecniche come il double buffering e le estensioni SIMD, che velocizzano notevolmente il trasferimento dei dati...
Penso che valga la pena approfondire questa strada!
Riferimenti
CBEA Handbook
Programming the CBEA, Examples and Best Practises
To be continued...
GRAZIE PER LA CORTESE ATTENZIONE