Cell Programming 2

Post on 23-Jun-2015

666 views 0 download

description

Secondo seminario in isislab

Transcript of 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