Laboratorul 8 – Lucrul cu Cell – IDE Eclipse si SDK 3.0

Scopul acestui laborator este familiarizarea cu mediul de programare IDE pentru arhitectura Cell/B.E. si contine: un tutorial pentru lucrul in mediul Cell – IDE Eclipse, notiuni despre crearea threadurilor si contextelor pentru SPEuri de catre PPE, cateva idei si un exemplu aplicat despre lucrul cu tipul vector in Cell/B.E. si are ca finalitate scrierea, compilarea si rularea unui program pentru inmultirea sirurilor de numere complexe.

Cuprins

1.   Tutorial Cell – IDE (Eclipse)

      1.1.  Cell/B.E. SDK 3.0: Crearea unui proiect SPU

      1.2.  Cell/B.E. SDK 3.0: Crearea unui proiect PPU

      1.3.  Cell/B.E. SDK 3.0: Crearea mediului de simulare pentru arhitectura Cell/B.E.

      1.4.  Rularea programului

2.   Crearea in PPE a threadurilor corespunzatoare SPE-urilor

      2.1. Crearea unui context SPE

      2.2.  Incarcarea unui executabil in Local Store-ul contextului SPE creat

      2.3.  Rularea contextului SPE

      2.4. Distrugerea contextului SPE

3.   Lucrul cu tipul vector in Cell/B.E.

      3.1.  Notiuni preliminare

      3.2.  Vectorizarea unei bucle

4.  Exercitii propuse

5.  Linkuri utile 

 

1. Tutorial Cell - IDE (Eclipse)

 

Observatii:

-          Pentru a cauta fisiere sau directoare in Linux, in directorul radacina, se poate folosi comanda:

 

find / -name eclipse

 

-          Cerinte minime recomandate de sistem pentru a lucra in Cell - IDE:

-          Hardware: sistem x86 cu procesor 2GHz Pentium® 4 (sau echivalent), minimum 1 GB RAM, minimum 9 GB spatiu liber pe HDD. In cazul folosirii a Full System Simulator, capacitatea minima a memoriei RAM trebuie sa fie dublul memoriei simulate. Pentru a simula un system cu 512MB de RAM, pe sistemul gazda trebuie sa existe cel putin 1GB de RAM.

-          Software: sistem de operare Linux Fedora Core 7 (dintr-un alt sistem de operare, se poate folosi imaginea de FC7 impreuna cu un program pentru lucru cu masini virtuale, ca VMware, vezi laboratorul precedent).

 

In continuare sunt prezentati pasii pentru crearea si rularea unui proiect Cell – IDE folosind Eclipse si mediul de lucru din imaginea de FC7 rulata prin VMware Server 1.0.5.

 

1.1. Cell/B.E. SDK 3.0: Crearea unui proiect SPU

 

Verificarea instalarii Cell IDE:

1. Mergeti in directorul in care a fost instalat Eclipse si porniti-l:

cd /home/student/eclipse (sau: cd /opt/IDE/eclipse/)

./eclipse

2. Click pe meniul de sus Help->Software Updates-> Manage Configuration, expandati /home/student/eclipse (sau: /home/cell_IDE/eclipse) si verificati ca exista:

·         Eclipse C/C++ Development Tooling SDK 3.1.2.200702150621

·         Eclipse Project SDK 3.2.2.r322_v20070104-dCGKm0Ln38lm-8s

·         IBM SDK for Multicore Acceleration IDE 3.0.0.2007100201

 

Crearea proiectului SPU:

1. Dupa deschiderea Eclipse, salvati workspaceul intr-o locatie dorita (de exemplu: /home/student/Desktop/workspace). Puteti copia ulterior intregul director, eliminand necesitatea urmarii pasilor urmatori de fiecare data pentru crearea unui proiect. Treceti apoi pe perspectiva C/C++ din: Window > Open Perspective > Other (Fig. 1).

 

Figura 1.jpg

Fig. 1. Schimbarea perspectivei

2. Alegeti C/C++ (Fig. 2) si faceti click pe OK.

 

Figura 2.jpg

Fig. 2. Selectarea perspectivei C/C++

 

3. Creati un nou proiect C mergand la: File -> New -> Project (Fig. 3).

 

Figura 3.jpg

Fig. 3. Crearea unui proiect nou pt SPU

4. Acum ar trebui sa va aflati in New Project Wizard. Expandati subsectiunea C si selectati Managed Make C Project. Un proiect Standard Make C/C++ necesita un makefile din partea programatorului, pe cand intr-un proiect Managed Make Project fisierul makefile este creat automat. Daca proiectul dvs are deja un makefile sau doriti sa creati propriul makefile, selectati Standard Make. Apasati pe Next pentru a continua.

 

5. Pentru numele proiectului, completati SPU, apoi apasati pe Next pentru a continua. La tipul proiectului selectati : Cell SPU Executable si apoi faceti click pe Finish.

 

6. In fereastra din stanga (view) numita C/C++ Projects, faceti dreapta-click pe proiectul SPU si selectati Properties (Fig. 4).

Figura 4.jpg

Fig. 4. Setarea proprietatilor proiectului SPU

7. La acest pas veti adauga directorele pentru compilatorul de SPU. Va trebui sa adaugati directorul ce contine fisierul header profile.h la lista cu cai incluse a compilatorului (necesar si pentru a putea folosi uneltele de analiza dinamica a performantei). Faceti click pe C/C++ Build in meniul din stanga. In tabul Tool Settings, selectati Directories din subsectiunea SPU GNU 32 bit C Compiler with Debug Options. In cadrul sectiunii Include Paths din dreapta, faceti click pe butonul Add (Fig. 5).

Figura 5.jpg

Fig. 5. – Adaugarea directoarelor pentru compilator

8. Mergeti la File System, apoi selectati calea: /opt/ibm/systemsim-cell/include/callthru/spu. Apasati apoi de doua ori pe OK pentru a reveni la ecranul principal.

 

9. Urmeaza crearea unui nou fisier sursa C din meniul File -> New -> Source File (Fig. 6). Ca nume, scrieti spu.c, apoi apasati pe Finish.

 

 

Figura 6.jpg

Fig. 6. Crearea unui nou fisier sursa

 

10. Pasul urmator consta in editarea continutului sursei. Faceti copy paste in fisierul spu.c, in fereastra de editare, la urmatoarea secventa de cod:

#include <stdio.h>

#include <profile.h>

 

int main(unsigned long long id)

{

//prof_clear();

//prof_start();

printf("Hello Cell (0x%llx)\n", id);

//prof_stop();

return 0;

}

 

11. Salvati sursa (CTRL+S). Se va face automat build. Informatiile de output sunt afisate in fereastra (view-ul) Console, in partea inferioara a ecranului (Fig. 7). Resursele noi, cum ar fi fisierele binare si cele incluse in proiect, pot fi vazute in fereastra (view-ul) C/C++ Projects din partea stanga (Fig. 7).

 

Figura 7.jpg

Fig. 7. Vedere dupa salvarea sursei si build automat

12. Felicitari! In acest moment ati terminat crearea unui proiect SPU.

 

1.2. Cell/B.E. SDK 3.0: Crearea unui proiect PPU

Pasii din aceasta subsectiune 1.2 este recomandat sa fie urmati dupa urmarea tuturor pasilor prezentati in subsectiunea 1.1 din acest laborator. In acest moment ar trebui sa aveti deschis Eclipse si perspectiva C/C++ selectata (vedeti pasii 1 si 2 din sectiunea 1.1, Crearea unui proiect SPU). Pentru a crea un proiect PPU parcurgeti urmatorii pasi:

 

1. Primul pas consta in crearea unui proiect PPU executable care va folosi modulul Embed SPU tool asupra proiectului SPU construit la punctul 1.1. Creati un nou proiect C mergand in meniul File -> New -> Project (Fig. 3).

 

2. Acum ar trebui sa va aflati in New Project Wizard. Expandati subsectiunea C si selectati Managed Make C Project. Un proiect Standard Make C/C++ necesita un makefile din partea programatorului, pe cand intr-un proiect Managed Make Project fisierul makefile este creat automat. Daca proiectul dvs are deja un makefile sau doriti sa creati propriul makefile, selectati Standard Make. Apasati pe Next pentru a continua.

 

3. Pentru numele proiectului, completati PPU, apoi apasati pe Next pentru a continua. La tipul proiectului selectati : Cell PPU Executable si apoi faceti click pe Next.

 

4. In urmatoarea fereastra va aparea proiectul creat in subsectiunea 1.1 (numit SPU). Acesta trebuie bifat deoarece executabilul din proiectul SPU va fi inclus in proiectul PPU. Bifati casuta din dreptul SPU si apoi faceti click pe Finish (Fig. 8).

 

 

Figura 8.jpg

 

Fig. 8. Includerea proiectului SPU in proiectul PPU

 

5. In fereastra din stanga (view) numita C/C++ Projects, faceti dreapta-click pe proiectul PPU si selectati Properties (Fig. 9).

 

Figura 9.jpg

 

Fig. 9. Setarea proprietatilor proiectului PPU

6. In panelul din stanga selectati C/C++ Build. In grupul Active configuration de sus, puteti schimba configuratia curenta, ca de exemplu ppu-gnu32-debug si ppu-xl32-debug, sau puteti crea propria configuratie. In Tabul Tool Settings, puteti manipula compilatorul, linker-ul, assembler-ul si functia SPU embedded (acest lucru e valabil doar pentru proiectele PPU). Faceti click pe Manage (Fig. 10) pentru a vedea in detaliu aceste optiuni.

Figura 10.jpg

 

Fig. 10. Alegerea optiunilor C/C++ build

 

7. Puteti redenumi, sterge sau crea configuratii noi. Selectati ppu-gnu32-debug apoi faceti click pe OK (Fig. 11).

 

Figura 11.jpg

 

Fig. 11. Manipularea configuratiilor de proiect PPU

 

8. In acest pas se vor adauga librariile pentru linker in PPU. In codul sursa PPU va fi inclus fisierul libspe2, astfel ca libraria spe2 va trebui adaugata la lista de librarii a linkerului. Mergeti in tabul Tool Settings, selectati optiunea Libraries din categoria PPU GNU 32-bit C Linker, apoi faceti click pe butonul de Add din sectiunea Libraries din partea dreapta (Fig. 12). Scrieti spe2 apoi apasati pe OK.

 

Figura 12.jpg

 

Fig. 12. Adaugarea librariei spe2

 

8. Urmeaza adaugarea executabilului din proiectul SPU in proiectul PPU. Pentru a include executabilul din proiect SPU in proiectul PPU, acesta va trebui adaugat folosind “embed SPU input”. In categoria PPU GNU 32-bit Embed SPU, selectati optiunea Inputs. Faceti apoi click pe butonul de Add din sectiunea (panelul) Embed SPU Inputs din partea dreapta. Apasati apoi pe butonul Workspace, apoi selectati executabilul SPU, mergend in structura arborescenta la: SPU -> spu-gnu-debug -> SPU. Apasati apoi OK de doua ori pentru a reveni la fereastra de proprietati a proiectului PPU si inca o data OK pentru a parasi fereastra de proprietati (Fig. 13).

Figura 13.jpg

Fig. 13. Adaugarea executabilului din proiectul SPU in proiectul PPU

9. Selectati PPU din panelul C/C++ Projects din partea stanga a ecranului. Urmeaza crearea unui nou fisier sursa C din meniul File -> New -> Source File (Fig. 6). Ca nume, scrieti ppu.c, apoi apasati pe Finish.

 

10. Pasul urmator consta in editarea continutului sursei. Faceti copy paste in fisierul ppu.c, in fereastra de editare, la urmatoarea secventa de cod:

#include <stdlib.h>

#include <stdio.h>

#include <errno.h>

#include <libspe2.h>

#include <pthread.h>

 

extern spe_program_handle_t SPU;

 

#define SPU_THREADS 8

 

void *ppu_pthread_function(void *arg) {

 

spe_context_ptr_t ctx;

unsigned int entry = SPE_DEFAULT_ENTRY;

ctx = *((spe_context_ptr_t *)arg);

 

if (spe_context_run(ctx, &entry, 0, NULL, NULL, NULL) < 0) {

perror ("Failed running context");

exit (1);

}

 

pthread_exit(NULL);

}

 

int main()

{

 

int i;

spe_context_ptr_t ctxs[SPU_THREADS];

pthread_t threads[SPU_THREADS];

 

/* Create several SPE-threads to execute 'SPU'. */

for(i=0; i<SPU_THREADS; i++) {

 

/* Create context */

if ((ctxs[i] = spe_context_create (0, NULL)) == NULL) {

perror ("Failed creating context");

exit (1);

}

 

/* Load program into context */

if (spe_program_load (ctxs[i], &SPU)) {

perror ("Failed loading program");

exit (1);

}

 

/* Create thread for each SPE context */

if (pthread_create (&threads[i], NULL, &ppu_pthread_function, &ctxs[i])) {

perror ("Failed creating thread");

exit (1);

}

}

 

/* Wait for SPU-thread to complete execution. */

for (i=0; i<SPU_THREADS; i++) {

 

if (pthread_join (threads[i], NULL)) {

perror("Failed pthread_join");

exit (1);

}

 

/* Destroy context */

if (spe_context_destroy (ctxs[i]) != 0) {

perror("Failed destroying context");

exit (1);

}

}

 

printf("\nThe program has successfully executed.\n");

return (0);

}

 

 

11. Salvati sursa (CTRL+S). Se va face automat build. Informatiile de output sunt afisate in fereastra (view-ul) Console, in partea inferioara a ecranului. Resursele noi, cum ar fi fisierele binare si cele incluse in proiect, pot fi vazute in fereastra (view-ul) C/C++ Projects din partea stanga.

12. Felicitari! In acest moment ati terminat crearea unui proiect PPU.

 

1.3. Cell/B.E. SDK 3.0: Crearea mediului de simulare pentru arhitectura Cell/B.E

Pasii din aceasta subsectiune 1.3 este recomandat sa fie urmati dupa urmarea in ordine a tuturor pasilor prezentati in subsectiunile 1.1 si 1.2 din acest laborator. In acest moment ar trebui sa aveti deschis Eclipse si perspectiva C/C++ selectata (vedeti pasii 1 si 2 din sectiunea 1.1, Crearea unui proiect SPU) si cele doua proiecte PPU si SPU create si configurate. Pentru a testa programul, mai intai trebuie creat mediul de simulare pentru arhitectura Cell/B.E. Cell/B.E. IDE integreaza simulatorul IBM full-system simulator pentru procesorul Cell/B.E. in Eclipse. Pentru a realiza acest task, parcurgeti urmatorii pasi:

 

 

1. In partea de jos a ecranului, selectati Cell Environments, faceti dreapta-click pe Local Cell Simulator si apoi faceti click pe Create ( Fig. 14).

 

Figura 14.jpg

Fig. 14. Inceperea procesului de creare a mediului de simulare

 

Obs: daca in partea de jos lipsesc taburi, le puteti aduce in felul urmator: din meniul Window -> Show View -> Problems; meniul Window -> Show View -> Console,; meniul Window -> Show View -> Properties; meniul Window -> Show View -> Other ->Optiunea Cell ->Cell Environments -> OK.

 

2. Urmeaza configurarea simulatorului. In acest moment ar trebui sa va aflati in fereastra de proprietati a Local Cell Simulator, dupa primul pas de mai sus. Puteti modifica oricand configuratia mediului simulatorului Cell/B.E. (atata timp cat acesta nu ruleaza) dand dreapta-click pe environment si selectand apoi Edit. Introduceti un nume pentru simulator (de exemplu: My Cell Simulator), apoi faceti click pe tabul Simulator (observati ca in tabul Hardware puteti seta cantitatea de memorie RAM din simulator) (Fig. 15).

Figura 15.jpg

Fig. 15. Configurarea simulatorului

3. In tabul Simulator bifati optiunea Show TCL console, apoi faceti click pe Finish (Fig. 15).

4. Urmeaza pornirea simulatorului. In tabul Cell Environments, Apasati pe semnul + din dreptul Local Cell Simulator (pentru a expanda continutul). Selectati apoi din lista aparuta numele simulatorului introdus la pasul 2 (in cazul nostru: My Cell Simulator), apoi apasati pe butonul Start the Environment din partea dreapta (sageata verde, cu forma de play) (Fig. 16). In acest moment va incepe lansarea simulatorului (poate dura pana la cateva minute) (Fig. 16).

5. Felicitari! In acest moment ati terminat crearea mediului de simulare Cell/B.E.

 

 

Figura 16.jpg

Fig. 16. Lansarea simulatorului Cell/B.E.

 

1.4. Rularea programului

Dupa terminarea incarcarii simulatorului, acesta va intra in pauza (PAUSED MODE). Pentru a rula programul PPU, trebuie parcursi urmatorii pasi:

1.Mai intai din fereastra de Linux systemsim-cell se selecteaza (optional) Mode->Fast, apoi se apasa pe butonul Go.

2. Dupa ce simulatorul va intra in RUNNING MODE, se merge in panelul C/C++ Projects din stanga si se face dreapta-click pe proiectul PP, apoi se face click pe Run As->Run... (exista si un buton de culoare verde, forma de play, in partea de sus sau se poate intra in meniul Run -> Run).

3. In meniul din partea stanga, se face dreapta-click pe optiunea C/C++ Cell Target Application apoi se da click pe New. Se selecteaza suboptiunea aparuta (SPU) si in partea dreapta, in tabul Main, in zona C/C++ Application se face click pe butonul Search Project. Apoi se selecteaza PPU din lista si se apasa butonul OK.

4. In cazul in care apare eroarea: „The CPU is not supported by selected debugger” se merge apoi la tabul Debugger (in dreapta) si se selecteaza Cell BE gdbserver gdb/mi folosind drop-down boxul din zona Debugger.

5. Se apasa apoi butonul Apply (partea dreapta, jos), urmat apoi de Run.

6. Se poate urmari outputul programului in fereastra Console din partea de jos a ecranului. Atentie: tot in partea inferioara, folosind butonul Display Selected Console, se poate comuta intre diferitele console din simulator (util si necesar pentru a vedea rezultatul unui program care si-a terminat rularea).

 

2. Crearea in PPE a threadurilor corespunzatoare SPE-urilor

In laboratorul trecut a fost prezentat un exemplu simplu de program in care se creau threaduri pentru SPE-uri. Acum vor fi explicate explicate pe scurt functiile folosite in acel program si parametrii acestora.

Pentru a porni SPE-urile din PPE, in programul PPE-ului am urmat 4 pasi:

1. Crearea unui context SPE.

2. Incarcarea unui obiect executabil pe SPE in local store-ul contextului SPE creat.

3. Rularea contextului SPE. Se transfera controlul sistemului de operare, care cere scheduling-ul efectiv al contextului pe un SPE fizic din sistem.

4. Distrugerea contextului SPE.

 

2.1. Crearea unui context SPE

-          spe_context_create este functia care creaza si initializeaza un context pentru un thread SPE care contine informatie persistenta despre un SPE logic. Functia intoarce un pointer spre noul context creat, sau NULL in caz de eroare. Exemplu:

#include <libspe2.h>

spe_context_ptr_t spe_context_create(unsigned int flags, spe_gang_context_ptr_t gang)

 

-          flags - Rezultatul aplicarii operatorului OR pe biti pe diverse valori (modificatori) ce se aplica la crearea contextului. Valori acceptate:

·         0 – Nu se aplica nici un modificator.

·         SPE_EVENTS_ENABLE – Configureaza contextul pentru a permite lucrul cu evenimente (!Foarte important pentru mailboxes – laboratorul urmator)

·         SPE_CFG_SIGNOTIFY1_OR – Configureaza registrul 1 de SPU Signal Notification pentru a fi in modul OR; default e in mod Overwrite (cu alte cuvinte, se va face o operatie logica OR intre noul semnal primit si cel deja existent, si nu o suprascriere)

·         SPE_CFG_SIGNOTIFY2_OR – analog SPE_CFG_SIGNOTIFY1_OR, pentru registrul 2 de SPU Signal Notification

·         SPE_MAP_PS –Pentru cerere permisiune pentru acces mapat la memoria "problem state area" (notata prescurtat PS) a threadului corespunzator SPE-ului. PS contine flagurile de stare pentru SPEuri si in mod default nu poate fi accesata decat SPE-ul propriu, iar din exterior doar prin cereri DMA. Daca acest flag e setat, se specifica la crearea contextului ca PPE vrea acces la memoria PS a respectivului SPE.

 

-          gang - Asociaza noul context SPE cu un grup(gang) de contexte. Daca valoarea pentru gang e NULL, noul context SPE nu va fi asociat vreunui grup.

 

2.2. Incarcarea unui executabil in Local Store-ul contextului SPE creat

Se realizeaza folosind functia cu urmatorul antet:

int spe_program_load(spe_context_ptr spe, spe_program_handle_t *program)

 

-          spe - un pointer valid al unui context SPE (intors de spe_context_create) in care se va incarca executabilul (programul specificat de urmatorul argument)

-          program - o adresa valida la un program mapat pe un SPE; in exemplul prezentat in laboratorul trecut, acesta era declarat ca extern spe_program_handle_t simple_spu, unde "simple_spu" era numele executabilului pentru SPU(SPE).

 

2.3. Rularea contextului SPE

Se realizeaza folosind functia cu urmatorul antet:

#include <libspe2.h>

 

int spe_context_run(spe_context_ptr_t spe, unsigned int *entry, unsigned int runflags,

void *argp, void *envp, spe_stop_info_t *stopinfo)

 

-          spe - Pointer catre contextul SPE care trebuie rulat

-          entry - Input: punctul de intrare, adica valoarea initiala a Intruction Pointer-ului de pe SPU, de unde va incepe programul executia. Daca aceasta valoare e SPE_DEFAULT_ENTRY, punctul de intrare va fi obtinut din imaginea de context SPE incarcata.

-          runflags - Diferite flaguri (cu OR pe biti intre ele) care specifica o anumita comportare in cazul rularii contextului SPE:

·         0 — Default, nici un flag.

·         SPE_RUN_USER_REGS — Registrii de setup r3, r4 si r5 din SPE vor fi initializati cu 48 octeti (16 pe fiecare din cei 3 registri) specificati de pointerul argp.

·         SPE_NO_CALLBACKS — SPE library callbacks pentru registri nu vor fi executate automat. Acestea includ si “PPE-assisted library calls” oferite de SPE Runtime library.

 

-          argp - Un pointer (optional) la date specifice aplicatiei. Este pasat SPE-ului ca al doilea argument din main (vezi figura).

-          envp - Un pointer (optional) la date specifice environmentului. Este pasat SPE-ului ca al treilea argument din main (vezi figura).

-          stopinfo - Un pointer (optional) la o structura de tip spe_stop_info_t (aceasta structura contine informatii despre modul in care s-a terminat executia SPE-ului)

Fig. 17. Comunicare PPU – SPU-uri prin parametrii functiei main

 

2.4. Distrugerea contextului SPE

Se face folosind functia cu urmatorul antet:

#include <libspe2.h>

 

int spe_context_destroy (spe_context_ptr_t spe)

 

Functia intoarce 0 in caz de succes, -1 in caz de eroare.

-          spe - Pointer spre contextul SPE care va fi distrus.

 

3. Lucrul cu tipul vector in Cell/B.E.

3.1. Notiuni preliminare


Un compilator care transforma automat scalari in structuri SIMD impachetate paralel este un compilator cu auto-vectorizare. Asemenea compilatoare trebuie sa manevreze toate constructiile unui limbaj de nivel inalt si din aceasta cauza rezultatul nu il constituie intotdeauna un cod optim.

O alta varianta, folosita in Cell, este ca vectorizarea sa se faca inca de la scrierea codului.

O prezentare a tipurilor de date vector a fost facuta in laboratorul 7. Mai jos este prezentat un tabel cu

Tabelul 1. Intrisincs SPU cu mapare unu-la-unu pe Vector/SIMD Multimedia Extension

SPU Intrinsic

Vector/SIMD Multimedia Extension Intrinsic

For Data Types

spu_add

vec_add

vector operands only, no scalar operands

spu_and

vec_and

vector operands only, no scalar operands

spu_andc

vec_andc

all

spu_avg

vec_avg

all

spu_cmpeq

vec_cmpeq

vector operands only, no scalar operands

spu_cmpgt

vec_cmpgt

vector operands only, no scalar operands

spu_convtf

vec_ctf

limited scale range (5 bits)

spu_convts

vec_cts

limited scale range (5 bits)

spu_convtu

vec_ctu

limited scale range (5 bits)

spu_extract

vec_extract

all

spu_genc

vec_addc

all

spu_insert

vec_insert

all

spu_madd

vec_madd

float only

spu_mulhh

vec_mule

all

spu_mulo

vec_mulo

halfword vector operands only, no scalar operands

spu_nmsub

vec_nmsub

float only

spu_nor

vec_nor

all

spu_or

vec_or

vector operands only, no scalar operands

spu_promote

vec_promote

all

spu_re

vec_re

all

spu_rl

vec_rl

vector operands only, no scalar operands

spu_rsqrte

vec_rsqrte

all

spu_sel

vec_sel

all

spu_splats

vec_splats

all

spu_sub

vec_sub

vector operands only, no scalar operands

spu_genb

vec_subc

vector operands only, no scalar operands

spu_xor

vec_xor

vector operands only, no scalar operands

 

Cateva dintre aceste functii pentru vectori insotite de explicatii:

·         vec = spu_splats(scal) - replica un scalar in fiecare element al unui vector
ex: vec1111 = spu_splats((float)1)

·         vec_float = spu_convtf(vec_int, scale) - converteste un vector de int intr-un vector de float

·         vec = spu_add(vec_a, vec_b) - adunare de vectori element cu element

·         vec = spu_sub(vec_a, vec_b) - scadere de vectori element cu element

·         vec = spu_mul(vec_a, vec_b) - inmultire de vectori element cu element (produs scalar)

·         vec = spu_madd(vec_a, vec_b, vec_c) - multiply (vec_a cu vec_b) si add (produsul se aduna cu vec_c);

·         vec = spu_nmadd(vec_a, vec_b, vec_c) - (multiply & add) negat

·         vec = spu_msub(vec_a, vec_b, vec_c) - analog madd, dar cu sub in loc de add

·         vec = spu_nmsub(vec_a, vec_b, vec_c) - analog nmsub, dar cu sub in loc de add

·         vec = spu_shuffle(vec_a, vec_b, vec_perm) - vec este rezultatul unui amestec (shuffle) controlat intre vec_a si vec_b; vec_perm specifica ce octeti din vec_a si din vec_b se vor afla in vectorul rezultat vec.

 

Tipuri de date de tip vector:

-          vector [unsigned] {char, short, float, double}

ex: "vector float", "vector signed short", "vector unsigned int", ...

-          Numarul de elemente din fiecare astfel de vector depinde de tipul elementelor. Trebuie tinut cont ca indiferent de tip, un vector are 128 biti. El contine astfel 4 * int, 4 * float, 8 * short, 16 * char ...

-          Se poate face cast intre diferite tipuri vector

-          Vectorii sunt aliniati la stanga in blocuri de dimensiunea quadword (16 octeti)

Pointeri la vectori :

-          Ex: "vector float *p"

-          p+1 e pointer spre urmatorul vector (16B) dupa vectorul la care refera p

-          Se poate face cast din pointeri la scalari si din pointeri la tipuri vector

 

3.2. Vectorizarea unei bucle

In continuare este prezentat un exemplu simplu de inmultire a doi vectori, element cu element. Programele (functia de inmultire si main-ul) sunt prezentate in varianta nevectorizata, in varianta vectoriala cand dimensiunile vectorilor sunt divizibile cu 4 (tipurile vector sunt pe 128 biti, deci contin 4 elemente pe 32 biti - in cazul nostru float) si in varianta vectoriala cand dimensiunile vectorilor nu sunt divizibile cu 4.

 

a)      Varianta nevectorizata

 

/* mult1.c */

 

#include <stdio.h>

int mult1(float *in1, float *in2, float *out, int N)

{

int i;

for (i=0;i<N;i++)

{

out[i] = in1[i] * in2[i];

}

 

return 0;

}

 

/* main.c /*

 

#include <stdio.h>

#define N 16

 

int mult1(float *in1, float *in2, float *out, int num);

 

float a[N] = { 1.1, 2.2, 4.4, 5.5,

6.6, 7.7, 8.8, 9.9,

2.2, 3.3, 3.3, 2.2,

5.5, 6.6, 6.6, 5.5};

 

float b[N] = { 1.1, 2.2, 4.4, 5.5,

5.5, 6.6, 6.6, 5.5,

2.2, 3.3, 3.3, 2.2,

6.6, 7.7, 8.8, 9.9};

 

float c[N];

 

int main()

{

int num = N;

int i;

mult1(a, b, c, num);

 

for (i=0;i<N;i+=4)

printf("%.2f %.2f %.2f %.2f\n", c[i], c[i+1], c[i+2], c[i+3]);

 

return 0;

}

# Makefile

# daca nu aveti CELL_TOP definit ca variabila globala, schimbati al doilea rand cu: include /opt/cell/sdk/buildutils/make.footer

PROGRAM_spu = example1

include $(CELL_TOP)/buildutils/make.footer

 

b)      Varianta vectoriala in care dimensiunea vectorilor initiali e multiplu de 4.

 

In functia de inmultire (mult1) vectorii de tip float se convertesc la vectori de vector float si se micsoreasa numarul de pasi din bucla (de 4 ori). Pentru inmultirea a doua variabile de tip vector (element cu element), se utilizeaza functia spu_mul(), din spu_intrinsics. Atentie, aici elementele c[i], a[i] si b[i] sunt vectori ce contin fiecare cate 4 float-uri.

 

/* mult1.c */

 

#include <stdio.h>

#include <spu_intrinsics.h>

int mult1(float *in1, float *in2, float *out, int N)

{

int i;

vector float *a = (vector float *) in1;

vector float *b = (vector float *) in2;

vector float *c = (vector float *) out;

 

int Nv = N >> 2; // N/4 -> fiecare vector float are 128 bytes =

// 4 * float pe 32 bytes

 

for (i=0;i<Nv;i++)

{

c[i] = spu_mul(a[i], b[i]);

}

return 0;

}

 

In main se aliniaza vectorii in memorie la limite de quadword (128 biti = 4 “cuvinte” pe 32 biti ).

 

/* main.c */

 

#include <stdio.h>

#define N 16

 

int mult1(float *in1, float *in2, float *out, int num);

 

float a[N] __attribute__ ((aligned(16)))

= { 1.1, 2.2, 4.4, 5.5,

6.6, 7.7, 8.8, 9.9,

2.2, 3.3, 3.3, 2.2,

5.5, 6.6, 6.6, 5.5};

 

float b[N] __attribute__ ((aligned(16)))

= { 1.1, 2.2, 4.4, 5.5,

5.5, 6.6, 6.6, 5.5,

2.2, 3.3, 3.3, 2.2,

6.6, 7.7, 8.8, 9.9};

 

float c[N] __attribute__ ((aligned(16)));

 

int main()

{

int num = N;

int i;

 

mult1(a, b, c, num);

 

for (i=0;i<N;i+=4)

printf("%.2f %.2f %.2f %.2f\n", c[i], c[i+1], c[i+2], c[i+3]);

 

return 0;

}

 

c)       Varianta vectoriala in care dimensiunea vectorilor initiali nu e multiplu de 4.

 

In main singura modificare facuta a fost asupra numarului de elemente din vectori(19), pentru a nu mai fi multiplu de 4. Observati  ca valoarea de la aliniere (numarul de biti) ramane tot 16.

In functia de inmultire (mult1) trebuie retinut catul (Nv) dar si restul (j) impartirii dimensiunii N la 4. Astfel, vor fi vectori de Nv elemente de tipul vector float, care se vor inmulti folosind functia spu_mul(), la fel ca la punctul b). Dar vom fi si j elemente (j<4) de tip float, care nu pot compune un vector float, si care vor trebui inmultite in modul traditional.

 

/* mult1.c */

 

#include <stdio.h>

#include <spu_intrinsics.h>

 

int mult1(float *in1, float *in2, float *out, int N)

{

int i;

vector float *a = (vector float *) in1;

vector float *b = (vector float *) in2;

vector float *c = (vector float *) out;

int Nv = N >> 2; // N/4 -> fiecare vector float are 128 biti =

// 4 * float pe 32 biti

int j = N % 4;

 

for (i=0; i<Nv; i++)

{

c[i] = spu_mul(a[i], b[i]);

}

 

for (i=N-j; i<N; i++)

{

out[i] = in1[i] * in2[i];

}

 

return 0;

}

 

/* main.c */

 

#include <stdio.h>

#define N 19

 

int mult1(float *in1, float *in2, float *out, int num);

 

float a[N] __attribute__ ((aligned(16))) //observati ca aici e tot 16, ca data trecuta

= { 1.1, 2.2, 4.4, 5.5,

6.6, 7.7, 8.8, 9.9,

2.2, 3.3, 3.3, 2.2,

5.5, 6.6, 6.6, 5.5,

1.1, 2.2, 3.3};

 

float b[N] __attribute__((aligned(16)))

= { 1.1, 2.2, 4.4, 5.5,

5.5, 6.6, 6.6, 5.5,

2.2, 3.3, 3.3, 2.2,

6.6, 7.7, 8.8, 9.9,

1.1, 2.2, 3.3};

 

float c[N] __attribute__((aligned(16)));

 

int main()

{

int num = N;

int i;

 

mult1(a, b, c, num);

 

for (i=0;i<N;i+=4)

printf("%.2f %.2f %.2f %.2f\n", c[i], c[i+1], c[i+2], c[i+3]);

 

return 0;

}

 

 

4. Exercitii propuse

1. Inmultirea sirurilor de numere complexe:

Se dau doua array-uri A si B de N (cu N divizibil cu 4) numere complexe. In fiecare array avem 2N componente de tip float; pentru fiecare numar complex avem partea reala, apoi partea imaginara (un exemplu de array care tine 4 numere complexe este: {re1, im1, re2, im2, re3, im3, re4, im4} ).

Folosind tipuri de date de tip vector si operatii cu aceste tipuri de date, sa se scrie un program care inmulteste fiecare numar complex din A cu numarul complex de pe aceeasi pozitie din B. Se va implementa o functie care rezolva problema cand N = 4 (2N = 8 numere float care intra in doua variabile de tip vector float) si aceasta functie se va apela din main() pentru dimensiuni mai mari ale problemei.

 

Reminder:

Fie doua numere complexe: z1 = a + i*b si z2 = c + i*d vom avea:

z1 * z2 = (a + ib)*(c + id ) = (ac − bd ) + i (ad + bc)


Se poate optimiza (in sensul ca scapam de o inmultire) calculul lui ad + bc, daca avem deja calculate ac si bd:

(a+b) * (c+d) - ac - bd = ac + ad + bc + bd -ac - bd = ad + bc

Folositi aceasta optimizare in programul vostru.


Hint: pe langa functiile aritmetice pentru tipuri de date de tip vector necesare, se va folosi intens functia: spu_shuffle() (vec = spu_shuffle(vec_a, vec_b, vec_perm))

Exemplu de utilizare a functiei spu_shuffle(): 

vector float vec_a = (vector float)(1,2,3,4); // fiecare numar e pe 32

// biti = 4 octeti

vector float vec_b = (vector float)(5,6,7,8);

vector float vec1, vec2;


//pentru shuffle, se considera ca primul argument contine octetii de la

//0 la 15, iar al doilea argument contine octetii de la 16 la 31


// vectorul de permutare contine numerele de ordine a 16 octeti din

// intervalul 0-31; acesti 16 octeti, in ordinea specificata in

// vectorul de permutare, vor fi continuti in vectorul rezultat


vector float vec_perm1 = (vector unsigned char)

(0,1,2,3,4,5,6,7,16,17,18,19,20,21,22,23);

 


vec1 = spu_shuffle(vec_a, vec_b, vec_perm1)

// in urma acestui apel vec1 = (1, 2, 5, 6)

 

vec1 = spu_shuffle(vec_b, vec_a, vec_perm1)

//in urma acestui apel vec1 = (5, 6, 1, 2)

 

vector float vec_perm2 = (vector unsigned char)

(0,1,2,3,16,17,18,19,4,5,6,7,20,21,22,23);

 

vec2 = spu_shuffle(vec_a, vec_b, vec_perm2)

//in urma acestui apel vec2 = (1, 5, 2, 6)

 

5. Linkuri utile:

1)      Informatii utile despre arhitectura CELL BE : http://www-01.ibm.com/chips/techlib/techlib.nsf/products/Cell_Broadband_Engine

2)      Imagine Fedora Core 7 cu Cell SDK 3.0: http://storage.grid.pub.ro/vmware-images/Cell-VMWare-F7-SDK30.zip

3)      VMware server download : http://register.vmware.com/content/download-a.html