Sunteți pe pagina 1din 577

Algoritmi paraleli si

distribuiți
1. Introducere
Cristian Mihăescu
cristian.mihaescu@edu.ucv.ro
Cuprins
• Aspecte fundamentale
• Introducere în calculul parallel si sistemele distribuite
• Platforme pentru calculul paralel
• Principii de proiectare ale algoritmilor paraleli
• Modelarea programelor paralele
• Programarea cu ajutorul trimiterii de mesaje
• Programarea cu ajutorul memoriei partajate
• Algoritmi paraleli/distribuiți
• Sortări, Căutări
• Algoritmi pe matrici dense
• Algoritmi pe grafuri, Programare dinamică
• Algoritmul *bakery*, problema generalilor bizantini, map-reduce
Motivația
• Paralelismul/distributivitatea reprezintă un pas înainte in ceea ce priveste
programarea calculatoarelor:
• Trecere de la gândirea algoritmică secvențială (en. Algorithmic thinking) la
gândirea paralelă (en. Parallel thinking).
• Multe calculatoare actuale au deja chip-uri multicore
• Utilizarea adecvată a calculatoarelor paralele este utilă în inginerie, știință,
afaceri, etc.
• Provocare intelectuală mare în zona CS: modele, limbaje de programare,
algoritmi, HW
• Multe probleme practice au solutii algoritmice paralelizabile. Acest fapt
permite accelerarea!
• Exemple: Prognoza vremii, mișcarea astrelor, genetică.
Domenii de aplicație care necesită APD
• Sănătate
• Inginerie
• Cercetare spațială
• Planificare urbană
• Finanțe și afaceri: Bitcoin

https://builtin.com/hardware/high-performance-computing-applications
Mae Rice, November 4, 2019, Updated: April 6, 2020
Tipuri de sisteme
• Calculatoare personale
• Nu sunt distribuite dar in prezent sunt multiprocesor
• Sisteme embedded
• Rulează pe un singur procesor sau pe un grup integrat de procesoare
• Sisteme distribuite
• Aplicația rulează pe un gup de sisteme cuplate/integrate slab (en., loosely
coupled/integrated) care cooperează cu ajutorul unei rețele.
Multicore
chip
Facts: 55th Anniversary of Moore's Law
https://www.infoq.com/news/2020/04/Moores-law-55
/
Moore’s
Law

https://en.wikipedia.org/wiki/Transistor_count
Elementele calculului paralel
• Hardware
• Mai multe procesoare
• Mai multe memorii
• Retea de interconectare de mare viteză
• Sistem de operare
• Sistem de operare care permite calcul paralel
• Elemente de programare care permit exprimarea/orchestrarea concurenței
• Aplicații software
• Biblioteci/sisteme care permit paralelizare/distribuție
SCOP: Utilizarea Hardware, Sistem de operare si Aplicațiilor software pentru:
1) obținerea accelerării (en., speedup)
2) rezolvarea de probleme care necesită o cantitate mare de memorie
Platforma de calcul paralel
• Organizarea logică
• Modul în care utilizatorul vede mașina prin intermediul sistemului de operare

• Organizarea fizică
• Arhitectura fizică a sistemului

• Arhitectura fizică este de obicei independentă de arhitectura logică


Scopul calculului paralel
• De obicei, arhitecturile convenționale sunt alcătuite dintr-un
procesor, o memorie, și o cale de acces între memorie și procesor
(en., datapath).
• Fiecare dintre aceste 3 componente poate deveni o gâtuire (en.,
bottleneck)
• Paralelismul adresează fiecare dintre aceste componente
• Fiecare aplicație poate integra aspecte diferite ale paralelismului:
• Aplicațiile care procesează cantități mari de date
• Aplicațiile server folosesc bandă largă
• Aplicațiile științifice necesită putere mare de calcul si memorie
Context
• Calculul paralel/distribuit este executarea simultană logică a mai
multor procese a căror execuție se poate întrețese (en., interleave) în
moduri diferite și complexe.
• Comunicarea si coordonarea între procese se realizează prin
transmiterea de mesaje (en., message passing) sau partajarea de
memorie (en., shared-memory) și cu ajutorul conceptelor algoritmice
ca atomicitate, consens, așteptare condiționată.
• Obținerea accelerării în practică necesită înțelegerea algorimilor
paraleli, a strategiilor de descompunere, a arhitecturii sistemelor, a
strategiilor de implementare, și a analizei performanțelor.
Context (cont.)
• Sistemele distribuite adresează problemele legate de securitate și
toleranta la defecte (en., fault tolerance), necesitatea întreținerii de
replici ale datelor și adresarea problemelor legate de rețeaua folosită.

• Toleranta la defecte se referă la abilitatea unui sistem (ex.,


calculator, rețea, cluster în cloud, etc.) de a-si continua rularea fără
întrerupere atunci când una sau mai multe componente se
defectează (en., fail).
Definiții / Termeni
• Paralelism = Utilizarea simultană de resurse de calcul (ex., procesoare,
memorii) pentru a obține accelerarea unui program sau a unei aplicații.
• Concurență = Administrarea eficientă și corectă a accesului la resurse de
calcul.
• Activitate/Task = calcul (en., computation) poate rula concurent cu alte
activități. Exemple: un program, un proces, un fir de execuție, sau un
echipament hardware dedicat.
• Atomicitate = Proprietatea unei activități de a fi indivizibilă, adică nu se
putea impărți în activități care să poată rula în paralel. De exemplu: setarea
biților într-un word, transmiterea unui singur pachet, finalizarea unei
tranzacții.
Definiții / Termeni (cont.)
• Aprobarea unanimă (en., Consensus) = Acordul între două sau mai
multe activități în ceea ce privește un predicat. De exemplu, valoarea
unui numărător, proprietarul unui zăvor (en., lock), sau terminarea
unui fir de execuție.
• Consistență (en., Consistency:) = Reguli si proprietăți convenite intre
activități în ceea ce privește valorile variabilelor sau mesajele trimise.
Această situație poartă denumirea de data race.
De exemplu, consistența secvențială stabilește că valorile tuturor
variabilelor din memoria partajată a unui program paralel sunt aceleași
cu ale unui singur program care realizează/permite accesul întrețesut la
memorie pentru activitățile definite.
Definiții / Termeni (cont.)
• Multicast = Reprezintă transmiterea unui mesaj către mai mulți
destinatari (en., recipients) fără nici o constrângere în ceea ce
privește ordinea în care sunt recepționate mesajele. Un eveniment
este un mesaj trimis către o mulțime stabilită de ascultători/abonați
(en., listeners or subscribers). [Notă: Observer pattern]
• Calculator paralel = un calculator cu mai multe procesoare care pot
lucra impreună pentru a rezolva o problemă de calcul.
• Implicatie: Notiunea de paralelism nu exista in situatia sismelor cu un singur
procesor.
1) toate procesoarele partajeaza aceeași memorie;
2) procesoarele comunică între ele accesând această memorie partajată
Definiții / Termeni (cont.)
• Program paralel = un program care se executa pe mai multe
procesoare in acelasi timp.
• Calculator distribuit = mai multe calculatoare/sisteme independente
conectate intr-o rețea
• Apar pentru utilizatori/aplicații ca un singur sistem (ex., rețea de calculatoare)
• Fiecare procesor are memoria lui proprie, ceea ce impiedică folosirea
memoriei partajate pentru comunicare.
• Procesoarele comunică prin transmiterea de mesaje.
• Cu toate ca transmiterea de mesaje este mai înceată decât memoria
partajată, transmiterea de mesaje se scalează mai bine pentru multe
procesoare si este o solutie mai ieftină.
Avantajele sistemelor distribuite
• Partajarea datelor: permite multor utilizatori accesul la aceleași date
în mod transparent.
• Partajarea resurselor: permite accesul mai multor utilizatori la
aceleasi periferice (ex., imprimantă).
• Comunicarea: Permite comunicarea simultană (ex., email, chat, etc.)
• Flexibilitate: Permite impărțirea sarcinilor pe mai multe sisteme
disponibile.
Avantajele sistemelor
distribuite (cont.)
• Prețul: O colecție de
sisteme/microprocesoare costă mai puțin
decât un calculator mainframe (ex., Sun
Enterprise 1000)
• https://www.cnet.com/products/sun-enterprise-100
00-ultrasparc-ii-400-mhz-monitor-none-series/
(~100K USD)
• Viteza: putere mai mare de calcul decât un
mainframe prin distribuția încărcării (en. Load
balancing).
Avantajele sistemelor distribuite (cont.)
• Distributivitatea inerentă/nativă: Unele aplicații sunt inerent/nativ
distribuite (ex., lanțurile de magazine).

• Siguranța/increderea în funcționare (en., reliable): Chiar dacă o


masină se defectează, intregul sistem va continua funcționarea
datorită redundanței datelor si conexiunilor.
Limitări ale sistemelor distribuite
• Software: Dificultatea dezvoltării de aplicații pentru sistemele
distribuite
• Rețea: Întârzieri, saturație, pierderea pachetelor
• Securitate: Acces facil la date.
• Aprobarea unanimă (en., consensus)
• Două noduri/sisteme trebuie să convină asupra unei valori utilizând
comunicarea prin mesaje prin canale nesigure (en., unreliable).
• Nodurile propun o valoare, dar unele se defectează sau nu raspund
• Algoritmul trebuie să se asigure că:
• Toate nodurile funcționale decid; Toate nodurile au aceeași valoare; Se alege valoarea
finală numai din valorile propuse.
Limitări ale sistemelor distribuite
• Transmiterea atomică (en., atomic broadcast)
• Un nod transmite mesajul
• Daca se primeste mesajul atunci un nod care funcționează corect retransmite inapoi
mesajul
• Toate nodurile care funcționeaza corect transmit același mesaj
• Mesajele sunt transmise în aceeași ordine
• In situația transmirerii atomice
• O putem folosi pentru a ajunge la consens (i.e., aprobare unanimă)
• Fiecare nod transmite o propunere
• Nodurile aleg prima propunere primită; mesajele sunt primite în aceeși ordine astfel
încât toate nodurile vor lua aceeași decizie
• Având consens îl putem folosi pentru a avea transmitere atomică
Concluzie: Transmiterea atomică este echivalentă cu consensul.
Exemplu: Problema celor 2 generali
https://en.wikipedia.org/wiki/Two_Generals%27_Problem
• Doi generali trebuie să coordoneze un atac
• Trebuie să agreeze un moment al atacului; - Înving doar dacă atacă simultan
• Comunică prin meageri; - Mesagerii pot fi uciși
• Solutie:
• P1. G1 trimite momentul atacului lui G2. Cum știm că G2 primeste mesajul?
• P2. G2 confirma primirea mesajului. Cum știm că G1 primeste confirmarea?
• Aceasta este prima problemă de comunicare între calculatoare care s-a
demonstrat ca nu are soluție în situația in care apar eșecuri (en., failures)
de comunicare în mod arbitrar.
1) Aceasta este situația în sistemele distribuite reale!
2) Problema generalilor Bizantini (generalizarea problemei celor 2 generali)
arata cand un sistem distribuit poate continua să funcționeze în ciuda
defectării unor componente.
Paralel sau distribuit?
Caracteristică Paralel Distribuit
Scop general Viteză comoditate
Interacțiuni Frecvente Rare
Granularitate Mare Mică
Siguranță în funcționare Presupus Posibil
(en., reliable) adevarată neadevătară
Calcul paralel: Eficiența se reduce la optimizarea comunicației între
procesoare, aceasta trebuie minimizată.
Calcul distribuit: Aspectul critic este dat de coordonarea între resurse
(ex., sisteme, noduri). Apare necesitatea redundanței din cauza
defectărilor (en. failures)!
Paralel/Distribuit
• Sistem paralel = Colecție de procesoare strâns conectate dintr-un singur
sistem. Fiecare procesor execută într-o manieră semi-independentă o
sarcina si se coordonează cu celelalte procesoare din timp în timp.

• Sistem distribuit = Colecție de sisteme autonome (care NU sunt strâns


conectate) care comunică într-o rețea de cat mai mare viteză și care
comunică pentru a îndeplini o sarcină.
Leslie Lamport: “A distributed system is one in which the failure of a
computer you didn't even know existed can render your own computer
unusable “
Obs: Procesarea paralelă în sistemele distribuite nu numai că este posibilă,
dar este și o opțiune bună în ceea ce privește costurile.
Scopul procesarii paralele/distribuite
• Fie T timpul necesar rezolvarii unei probleme pe un singur procesor.
• Dacă avem la dispozitie p procesoare, am putea obține o accelerare
care să permită rezolvarea problemei in T/p timp?
• Este acest lucru posibil in practică?
• Care sunt limitarile? Coordonarea, comunicarea, balansarea.
• Exemplu de problemă: Determinati toate elementele negative
dintr-un tablou
• Impartim tabloul in segmente in functie de numarul de procesoare.
• Fiecare procesor determină numarul de elemente negative din segmentul alocat.
• La final, adunăm toate valorile detrminate.
De la secvențial la paralel

Parallel Computing Laboratory at UC Berkeley: “Writing programs that scale with increasing numbers of
cores should be as easy as writing programs for sequential computers”
https://www.appentra.com/parallware-method-assisted-parallelism-discover-parallel-patterns/
Dificultăți în procesarea paralelă
• Depanarea (en., debugging)
• Dificultatea apare din cauza greutății cu care putem replica situatia apărută.
Refacerea/simularea contextului în care a aparut o eroare este în multe situații o
operatie dificil de realizat. Acest lucru se datorează intrețeserii (en., interleaving).
• Corectitudinea
• Demonstrarea corectitudinii unui algoritm paralel este o provocare.
• Incorectitudinea poate conduce la *undefined behaviour* iar depanarea este de
obicei dificilă.
• Benchmark
• Evaluarea performațelor (i.e., accelerarea) prin rulari/simulari este aspectul
principal. La programarea secventuală (mai ales pe input-uri mici) acest aspect este
de multe ori (incorect!) ignorat.
Dificultăți în procesarea paralelă (cont.)
• Comunicarea între procesoare
• Dificultatea abordarii paralele a algoritmului/procesării și ulterior dezvoltarea,
testarea si implementarea soluției.
• Dintre soluții:
• Proiectarea optimă și integrată a procesoarelor/memoriilor.
• Apariția și dezvoltarea microchip-urilor multiprocesor.
• Apariția de modele standard de programare paralela si comunicare in diverse limbaje de
programare la diverse nivele:
• Limbaje de programare: C/C++, Java, C#, etc.
• Sisteme de programare: OpenMP, MPI
• Sisteme pentru GPU: CUDA, OpenCL, OpenACC
• Folosirea HW comun si standardizarea SW
• NU este o buna practică:
• Utilizarea sistemelor paralele pe aplicații secvențiale.
• Paralelizarea aplicațiilor fară să avem sisteme paralele la dispoziție.
Contributori
• Dijkstra, Edsger W. (1972 ACM Turing Award) and C.S. Scholten, Predicate
Calculus and Program Semantics, Texts and Monographs in Computer
Science. Springer-Verlag, 1989.
• Susține “calculational proof style” pentru argumentarea matematică. A utilizat
metoda propusă în geometrie, algebră liniară, teoria grafurilor, proiectarea
programelor secvențiale si distribuite.
https://amturing.acm.org/award_winners/dijkstra_1053701.cfm
• Hoare, Antony C. (1980 ACM Turing Award)
- Propune limbajul CSP în care interacțiunea dintre programe era limitată de
comunicații prestabilite. CSP devine ulterior aplicație si apoi transputer
(https://en.wikipedia.org/wiki/Transputer ).
- Transputer = serie de microprocesoare din anii 1980 care are memorie si
comunicare serială proiectată în vederea calculului paralel.
https://amturing.acm.org/award_winners/hoare_4622167.cfm
Contributori
• Dennis M. Ritchie (1983 ACM Turing Award) : A proiectat sistemul de
operare distribuit Inferno și limbajul de programare Limbo în 1995. Inferno
a fost proiectat pentru cutiile de cabluri ale televizoarelor (en., television
set-top boxes) și pentru telefoanele avansate.
https://amturing.acm.org/award_winners/ritchie_1506389.cfm

• Fernando Corbato (1983 ACM Turing Award): ” For his pioneering work
organizing the concepts and leading the development of the
general-purpose, large-scale, time-sharing and resource-sharing computer
systems, CTSS and Multics.”
https://amturing.acm.org/award_winners/corbato_1009471.cfm
Contributori
• Milner Robin Arthur (1991 ACM Turing Award):
CCS: Teoria generală a concurenței
Milner, R., A Calculus for Communicating Systems, Lecture Notes in
Computer Science, Vol. 92, Springer, 1980.
https://amturing.acm.org/award_winners/milner_1569367.cfm
• Butler Lampson (1992 ACM Turing Award):
”For contributions to the development of distributed, personal
computing environments and the technology for their implementation:
workstations, networks, operating systems, programming systems, displays,
security and document publishing.”
https://amturing.acm.org/award_winners/lampson_1142421.cfm
Contributori
• Vinton G. Cerf și Robert E. Kahn (2004 ACM Turing Award):
”led the design and implementation of the Transmission Control
Protocol and Internet Protocol (TCP/IP) that are the basis for the
current internet. ”
In 1983, Kahn a propus ”ARPA’s Strategic Computing Initiative”
unprogram de cercetare de 1mld USD care includea proiectarea de
chip-uri, arhitecturi de calcul paralel, si inteligență artificială.
https://amturing.acm.org/award_winners/cerf_1083211.cfm
Contributori
• Barbara Liskov (2008 ACM Turing Award):
” For contributions to practical and theoretical foundations of programming
language and system design, especially related to data abstraction, fault
tolerance, and distributed computing. ”
https://amturing.acm.org/award_winners/liskov_1108679.cfm
• Leslie Vailant (2010 ACM Turing Award):
” For transformative contributions to the theory of computation, including
the theory of probably approximately correct (PAC) learning, the complexity
of enumeration and of algebraic computation, and the theory of parallel and
distributed computing.”
https://amturing.acm.org/award_winners/valiant_2612174.cfm
Contributori
• Leslie Lamport (2013 ACM Turing Award):
” For fundamental contributions to the theory and practice of distributed and
concurrent systems, notably the invention of concepts such as causality and
logical clocks, safety and liveness, replicated state machines, and sequential
consistency. ”
https://amturing.acm.org/award_winners/lamport_1205376.cfm
Contribuții:
- Algoritmul Bakery
- Bazele programării concurente: "loop freedom”, consistența secvențială, registri atomici.
- Bazele sistemelor distribuite: ceasuri logice, înțelegerea Bizantină (en., Byzantine
Agreement), Replicarea masinilor cu stări (State Machine Replication )
- Verificarea și specificarea formală a programelor
- LaTeX
Contributori [alte domenii]
• 2014: Michael Stonebraker: ”... modern database systems”
• 2015: Diffie Whitfield: ” asymmetric public-key cryptography, ...
digital signatures, and a practical cryptographic key-exchange
method”
• 2016: Barnes-Lee-Tim: ”For inventing the World Wide Web”
• 2018: Bengio, Hinton, LeCun: ”deep neural networks a critical
component of computing”
• 2019: Catmull, Hanrahan: ” 3D computer graphics, and the impact of
computer-generated imagery (CGI) in filmmaking”
Cine si ce?
• https://top500.org/ oferă statistici despre sistemele paralele
existente: producători, utilizatori, locații, aplicații, etc.
Evaluare
• Laborator: 40% [minim nota 5]
• MPI: http://software.ucv.ro/~cpoteras/pda/index.html
• Paralelism in C++ : Threads / STL Paralel
• Ștefan Popescu (4sg.) + Renato Ivănescu (2sg.)
• Proiect: 30% : Rezolvă o problema folosind:
1) Limbaje de programare:
- POSIX Threads, Pthreads library for C ; C++ : Java-Style Synchronization ; C++ Threads, Parallel STL C++ algorithms; Java:
Threads, synchronized methods, external locks, java.util.concurrent; python parallel computing; C# parallel computing
2) Sisteme/Frameworks de programare (en., programming system)
-OpenMP; MS-MPI (https://docs.microsoft.com/en-us/message-passing-interface/microsoft-mpi ) ; MPI-3, AdaptiveMPI, Charm++
; MPJ Express (http://mpj-express.org); PVM.
3) Sisteme/Frameworks de programare cu GPU
- CUDA/OpenACC/OpenCL
4) Alte tehnologii/unelte: Blockchain, Hadoop.
• Examen oral: 30% [minim nota 5]
- exmen partial la mijlocul semestrului
Evaluare: Proiect [30%]
• S1->S3 [2 sapt.] – Stabilirea temei: enunț, limbaje de programare,
frameworks utilizate.
• S3->S12 [10 sapt.]: Dezvoltarea soluțiilor
• Cerințe minime:
• soluție secvențială
• 2-3 abordări paralele: limbaje de programare, sisteme, etc.
• Comparatii ale accelerării în funție de dimensiunea intrării, limbaj, sistem, etc.
• Minim 3 prezentări intermediare (S6, S8, S10)
• S12->S14[2 sapt.]: Evaluarea proiectelor
• În cadrul laboratorului/cursului
Exemple de probleme
• Probleme cu matrici: inmultirea unui vector cu o matrice, inmultirea
matricelor, rezolvarea sistemelor de ecuatii liniare
• Probleme pe grafuri: arbori partiali de cost minim, drumuri minime
(SSSP, APSP), componente conexe, etc.
• Sortări
• Căutări
• Geometrie: înfășurătoarea convexă (en., convex hull), cele mai
apropiate două puncte în plan (en., closest pair of points).
• ETC...
Referințe
• Computer Science Curricula 2013, Parallel and Distributed Computing (PD),
https://www.acm.org/binaries/content/assets/education/cs2013_web_final.pdf
• Curriculum Initiative on Parallel and Distributed Computing - Core Topics
for Undergraduates, http://tcpp.cs.gsu.edu/curriculum/
• Lynch, N. A. (1996). Distributed algorithms. Elsevier.
• Grama, Ananth, Vipin Kumar, Anshul Gupta, and George
Karypis. Introduction to parallel computing. Pearson Education, 2003.
https://www-users.cs.umn.edu/~karypis/parbook/
• Designing and Building Parallel Programs, by Ian Foster
https://www.mcs.anl.gov/~itf/dbpp/
• Livermore Computing Center Training Materials
• https://hpc.llnl.gov/training/tutorials
Algoritmi paraleli si
distribuiți
Tipuri și nivele de paralelism
+POSIX
Tipuri de procesări paralele
Taxonomia lui Flynn:
• SISD – Single Instruction stream, Single Data stream
• SIMD - Single Instruction stream, Multiple Data stream
• MISD - Multiple Instruction stream, Single Data stream
• MIMD - Multiple Instruction stream, Multiple Data stream

Flynn, M. J. (1972). Some computer organizations and their


effectiveness. IEEE transactions on computers, 100(9), 948-960.
SISD – Single Instruction stream, Single Data stream

• Un singur procesor, intr-un singur sistem care are o singură memorie.


• Instrucțiunile se execută secvențial.
• Procesarea paralelă se poate realiza numai cu ajutorul a mai multor
unități funcționale (i.e., mai multe sisteme) si/sau prin procesare
pipeline.
SIMD – Single Instruction stream, Multiple Data stream

• Reprezintă un sistem cu mai


multe procesoare care se află
sub comanda unei unități de
control comune.
• Toate procesoarele
primesc/execută aceleași
instrucțiuni, dar pe date diferite.
SIMD
• Avantaje
• Mai puțin HW necesar față de MIMD, este nevoie doar de o unitate de
control (care poate avea mai multe procesoare).
• Este nenesară mai puțină memorie deoarece este nevoie doar de o singură
copie a programului.
• Comunicare minimală între procesoare.
• Ușor de înțeles.
• Dezavantaje
• Infrastructură rigidă care se pretează numai pentru problemele bine
structurate.
SIMD - exemplu
• Sistemele SIMD sunt eficiente pentru programele realizează aceeasi procesare pe
multe date în paralel.
• Ex. 1. Procesorul k execută c[k] = a[k]+b[k]
for (i=0; i<1000; i++)
c[i] = a[i]+b[i];
• Ex. 2. Procesoare distincte nu pot executa instrucțiuni distincte în același ciclu
processor.
for (i=0; i<10; i++)
if (a[i]<b[i])
c[i] = a[i]+b[i];
else
c[i] = 0;
MISD –Multiple Instruction stream, Single
Data stream
• Procesoarele primesc/execută instrucțiuni diferite, dar lucrează pe
aceleași date.
• Acest model este teoretic!
MIMD – Multiple Instruction stream, Multiple Data stream

• Sistemul este capabil să ruleze mai multe programe în același


timp.
• Cele mai multe sisteme multiprocesor aparțin acestei categorii.
• Fiecare procesor poate executa un flux separat de instrucțiuni
asupra unor date proprii pe care le gestionează local.
• Acest model este cu memorie distribuită deoarece memoria
este distribuită între procesoare în loc să fie partajată (i.e.,
plasată central si accesibilă tuturor procesoarelor).
MIMD
• Avantaje:
• Poate fi ușor/rapid/ieftin construit pe baza unor microprocesoare existente.
• Flexibilitatea: potrivit pentru probleme care nu se inscriu în anumite șabloane.
• Poate utiliza alt HW pentru a obține sincronizare rapidă, ceea ce poate face ca
sismemu MIMD sa opereze in modul SIMD.
• Dezavantaje
• Complexitate crescută (fiecare procesor are propria unitate de control).
• Necesită mai multe resurse (duplicarea programului, OS, etc.).
• Proiectarea/depanarea mai dificilă a programelor.
PRAM: Parallel Random Access Machine
• Sistemul este multiprocesor dar cu memorie partajată.
• Toate procesoarele au acces la o memorie comună care este
partajată între ele cu ajutorul unei magistrale de date (en., bus
network).
• Modelul ideal PRAM este folosit în studiile teoretice ale algoritmilor
paraleli. Acest model permite accesarea memoriei de către fiecare
procesor cu aceeași viteză (i.e., în aceeași cantitate de timp).
• Este omologul modelului von Neuman pentru sistemele
secvențiale.
PRAM: Parallel Random Access Machine
(cont.)
• Este compus din:
• p rocesoare, care rulează sincron aceleași
instrucțiuni.
• Fiecare procesor are o memorie locală.
• Fiecare procesor este conectat la memoria
partajată.
• Accesul la memoria partajată se face în timp
constant, intr-un pas.
• Limitarea modelului PRAM
• Nu ia în calcul comunicarea și se concentrează
doar pe sarcinile paralele.
PRAM: Parallel Random Access Machine
(cont.)
• In practică, scalarea sistemelor cu multiprocessor necesită
introducerea/utilizarea unei arhitecturi ierarhice a memoriei.
• Frecvența cu care este accesată memoria partajată se poate reduce
prin crearea de chache-uri
• Chache = copie ale datelor frecvent folosite care se gasește de fiecare
procesor.
• Obs: Accesarea cache-ului este mult mai rapidă decât accesarea memoriei
partajate => așezarea/localizarea datelor este importantă!
• Aspecte practice referitoare la crearea si administrarea unui cache:
• Ce structura de date sa utilizam? Array, vector, heap, arbore, graf?
• Care sunt cele mai dese operatii? Inserare, căutare, modificare, stergere?
Alte clase de sisteme
• .... Care se pot folosi uneori ca și sisteme paralele:
• LAN (Local Area Network): sistemele sunt fizic apropiate (ex., intr-o
cladire) și sunt conectate intr-o rețea (rapidă!).
• WAN (Wide Area Network): sistemele sunt distribuite geografic la
mare distanță.
Alte clasificări/taxonomii
• Duncan
• Propusa de Ralph Duncan in 1990.
• Modifică taxonomia Flynn prin introducerea noțiunii de proces vectorial de
tip pipeline (en., pipelined vector processes)
• Tse-yun Feng
• Propune utilizarea noțiunii de grad de paralelizare pentru a clasifica diferitele
arhitecturi ale claculatoarelor paralele.
• Gradul de paralelizare poate fi maxim, mediu, minim.
• Introduce noțiunea de utilzare a procesorului.
Organizarea fizică a sistemelor
Rețelele de interconectare
• Asigură conexiunile procesor-la-procesor și procesor-la-memorie
• Sunt clasificate în:
• Statice
• Constă dintr-un număr de legături punct-la-punct: rețea directă.
• Sunt folosite pentru a conecta procesoarele între ele: sisteme cu memorie distribuită.

• Dinamice
• Rețea indirectă: rețeaua constă din elemente la care se atașează procesoarele.
• Sunt folosite pentru a conecta procesoarele cu memoria: sisteme cu memorie partajată.
Rețea de conectare statică/dinamică
Metrici de evaluare pentru rețelele de
interconectare
• Diametrul [cu cât mai mic cu atât mai bine]
• Lungimea maximă dintre oricare două noduri.
• Conectivitatea [cu cât mai mare cu atât mai bine]
• Numărul minim de conexiuni care trebuie distruse pentru a obține două
componente conexe din rețeaua inițială.
• Determină numărul de căi multiple.
• Lățimea bisecțiunii (en., bisection width) [cu cât mai mare cu atât mai
bine]
• Numărul minim de conexiuni care trebuie distruse pentru a obține două
componente conexe de dimensiune egală.
Metrici de evaluare pentru rețelele de
interconectare (cont.)
• Lățimea de bandă a bisecțiunii (en., bisection band width) [cu cât mai
mare cu atât mai bine]
• Se aplică pentru rețele ponderate (en., weighted). Ponderile corespund lățimii
de bandă, adică reprezintă cantitatea de date care poate fi transferată prin
acea legătură/muchie.
• Volumul minim de date care pot fi transferate între oricare două jumătăți ale
rețelei.
• Costul [cu cât mai mic cu atât mai bine]
• Numărul de legături (i.e., arce) din rețea.
- Care algoritmi de grafuri adresează aceste probleme?
Metrici în rețelele
dinamice
• Lățimea bisecțiunii într-o rețea dinamică se
determină prin examinarea mai multor tăieturi
care împart rețeaua în două mulțimi egale de
noduri.
• Lațimea bisecțiunii este numărul minim de muchii
care traversează taietura.
• În figura alăturată:
• Toate tăieturile sunt traversate de 4 muchii.
• Lățimea bisecțiunii acestul graf este 4.
Topologiile rețelelor

Rețele bazate pe magistrale (en., bus-based)


• Partajare minimă.
• Datele sunt transmise tuturor procesoarelor
(i.e., broadcast).
• Evaluare:
• Diametru: O(1)
• Conectivitate: O(1)
• Lățimea bisecțiunii: O(1)
• Costul: O(p)
Topologiile rețelelor

Rețele bazate pe traversare (en.,


crossbar)
• Folosesc comutatoare
• Permit conexiuni simultane
• Evaluare:
• Diametru: O(1)
• Conectivitate: O(1)
• Lățimea bisecțiunii: O(p)
• Costul: O(p^2)
Topologiile rețelelor

Rețele bazate pe interconectare pe nivele/faze


Topologiile rețelelor

Rețele bazate pe interconectare pe nivele/faze cu comutatoare


Topologiile rețelelor
Topologiile rețelelor

•Topologii carteziene:
•Liniare
•2-D
•3-D
Topologiile
rețelelor

- Hipercuburi
Topologiile rețelelor: structuri arborescente
Organizarea fizică
• Coerența cache-urilor în sistemele cu memorie partajată
• Un anumit nivel de consistență trebuie asigurat atunci când avem mai multe
copii ale acelorași date.
• Este necesară pentru a asigura execuția corectă a programului
• Serializarea datelor
• Sunt folosite două protocoale pentru a asigura coerența cache-urilor
• Invalidarea
• Update-ul
Organizarea fizică

Coerența cache-urilor în sistemele cu memorie partajată: a) invalidarea, b) update-ul


Protocoalele Invalidare/Update
• Metoda folosită depinde de caracteristicile aplicației: frecvența
operațiilor de citire/scriere a variabilelor din memoria partajată
• Negocierea (en., trade-off) dintre:
• Efectuarea update-urilor care introduce nevoie mare de comunicare
și
• Funcționarea în gol (en., idling) prin netrimiterea invalidării
• Pot aparea probleme din cauza falsei partajări
• Protocolul de invalidate este mai des utilizat
• Au fost dezvoltate mai multe abordări pentru menținerea/gestionarea stării
datelor partajate.
Costurile de comunicare în sistemele paralele
Sistemele cu transmitere de mesaje
- Costul de comunicare pentru o operație de transfer de datel depinde
de:
- Ts : timpul de inițializare: adaugarea header-ului, executarea algoritmului de
routare, stabilirea conexiunii dintre sursă și destinație
- Th : timpul necesar transferului între două noduri adiacente, adică latența.
- Tw : timpul necesar transferului unui cuvânt, este 1/lățimea calalului de
comunicație
Model clasic de comunicare
• Costul trimiterii unui mesaj de dimensiune m este

T com_m = Ts + Tw * m

• Reprezintă un model suficient de realist deoarece de obicei Ts este


mult mai mare decât Th, iar pentru cei mai mulți algoritmi m * Tw
este mult mai mare decât l * Th
Mecanisme de routare
• Routarea este algoritmul folosit pentru determinarea căii pe care o va
parcurge un mesaj de la sursă la destinație
• Poti fi clasificate astfel:
• Minimale/optime vs. Suboptimale
• Deterministe vs. Adaptive
• Dezvoltarea algoritmilor de routare specifici topologiei
• Se are în vedere înglobarea metricilor de calitate
• Dilatarea = numărul maxim de linii atribuite unei muchii/conexiuni
• Congestionarea = numărul maxim de muchii atribuite unei singure legături
Accelerarea (en. speedup)

Abordări ale calculului paralel: clasificare
• Pipelining: instrucțiunile sunt descompuse în operații elementare/atomice;
astfel, mai multe operații se pot executa simultan.
• Paralelism funcțional: permite unor sisteme (ex., procesoare)
independente să execute instrucțiuni/funcții distincte/specializate.
• Paralelism vectorial: permite unor sisteme (ex., procesoare) identice să
execute aceeași instrucțiune/funcție pe date diferite în cadrul aceleiași
logici de control.
• Multi-procesare: mai multe procesoare *strâns cuplate* execută
instrucțiuni indeendente și comunică prin memorie partajată.
• Multi-calcul: mai multe procesoare *strâns cuplate* execută instrucțiuni
indeendente și comunică prin mesaje.
Concurență vs. Paralelism
• Concurența: situația în care se
află un sistem în care mai multe
task-uri sunt logic active la un
moment dat.
• Paralelismul: situația în care se
află un sistem în care mai multe
task-uri sunt fizic active la un
moment dat.
*Programele paralele* includ *Programele concurente*
Concurență vs. Paralelism
• Aplicație concurentă = aplicație în care task-urile se execută simultan
prin semantica aplicației. Aceasta este asigurată prin proiectare
adecvată a soluției la o problemă care este intrinsec concurentă.

• Aplicație paralelă = Aplicație în care task-urile se execută efectiv


simultan astfel încât timpul de rulare să scadă. Problema rezolvată nu
trebuie neaparat să fie intrinsec concurentă, ea se poate exprima în
mod secvențial.
Pipelining: Exemplu

• Avem o spalătorie care are o masină de spălat, una de


uscat si un angajat.
• Timpul total de spălare pentru un coș de rufe este 90 de
minute:
• Spălare: 30 de minute
• Uscare: 40 de minute
• Împachetarea efectuată de angajat: 20 de minute
Pipelining: Exemplu: Spălarea secvențială
• Angajatul efectuază
impachetarea o dată la 90 de
minute. Astfel, el nu va începe o
nouă impachetare decât dupa
ce un ciclu de spalare-uscare s-a
terminat.
• Execuția acestui proces
secvențial necesită 6 ore pentru
4 coșuri de rufe.
Pipelining: Exemplu: Spălarea eficientă (angajatul
împachetează rufele imediat)
• Un alt operator
realizează încarcarea
rufelor în masina de
spalat la fiecare 40 de
minute?
• Ce se întâmplă daca
realizează încărcarea
mașinii de spălat imediat
după ce aceasta termină
spălarea?
• Timpul necesar spălării
pentru 4 coșuri devine
3.5 ore. Este acesta
timpul optim?
Pipelining: Concluzii
• Mai multe sarcini distincte se
desfășoară în paralel.
• Nu se îmbunătățește execuția unei
sarcini, ci a intregului proces.
• Limitarea este dată de cel mai lentă
sarcină.
• Accelerarea potențială este dată de
numarul de stadii ale pipeline-ului.
• Diferența mare între timpul de
execuție al sarcinilor conduce la
diminuarea accelerării.
• Trebuie să avem în vedere timpul
necesar *umplerii* si *golirii*
pipeline-ului.
Pipelining: Concluzii
• Metoda pipeline descompune un proces secvențial în
segmente/sarcini/task-uri distincte ca tip/procesare.
• Dedică un procesor (ex., hardware) unui anumit tip de task.
• Fiecare task este executat pe procesorul dedicat concurent cu celelalte
stak-uri.
• Rezultatele/informația trebuie transmise între procesoare (ex., hardware).
• În general, sarcina totala este împărțită în k segmente/stagii. Toate
segmentele necesită aceeași cantitate de timp, adică un ciclu al
procesorului. Durata unui ciclu al procesorului este determinată de cel mai
încet segment.
Pipelining: Exemplu
numeric
• Avem 3 fluxuri de numere A, B si C
• Dorim sa calculăm Ai * Bi + Ci pentru i=1,
2, 3, ... 7
• Definim urmatoarele operații/segmente
• R1 <- Ai, R2 <- Bi
• R3 <- R1 * R2 R4 <-Ci
• R5 <- R3 +R4
Pipelining: Exemplu numeric (starea
regiștrilor)
Performanța pipeline-ului
• N = dimensiunea problemei (ex., numarul de coșuri cu rufe, dimensiunea fluxurilor de
numere A, B si C)
• K = numarul de segmente din pipeline (ex., 3 la spălătorie [spălare, uscare, împachetare],
3 la exemplul numeric [R1/R2, R3/R4 și R5])
• T = timpul necesar unui ciclu al procesorului (este timpul necesar execuției celui mai încet
task [40 min la spalatorie], segmentul 2 la exemplul numeric).
• Tk = timpul total necesar procesării Tk = (K + (N-1)) * T
• Accelerarea
Speedup = T1/Tk = N*K / (K + (N-1))
Ex: Pentru K=3 si N=4 avem nevoie de K cicluri pentru umplerea pipeline-ului, apoi (N-1)
rezultate vor fi disponibile la fiecare ciclu, apoi (K+N-1) cicluri vor fi necesare pentru
finalizarea sarcinii. Executia secvențială necesită K*N cicluri ale procesorului.
Obs. Pentru N >>> K (N=1 mil de fisiere, K=3 segmente), Speedup ≈ k. Majoritatea procesării
se realizează în parallel, mai putin umplerea și golirea pipelien-ului.
Modelul de programare cu fire de execuție
(POSISX )
• Toată memoria este globală și poate fi accesată de toate firele de
execuție (en., threads)
• Stiva este locală dar poate fi partajată
• Firele POSIX (API/Pthreads)
• Nivel jos de programare
• Au apărut ca standard susținut de cele mai multe SO, acest lucru asigura
portabilitatea.
• Oferă funcții care perimit crearea, terminarea si sincronizarea firelor
• Aceste funcții sunt de nivel jos care nu oferă modalități de nivel înalt pentru partajarea
eficiantă a datelor
• Nu există noțiunea de comunicare colectivă ca aceea oferită de MPI.
PThreads
• Crearea si finalizarea firelor
• Primitive de sincronizare
• Lacăte pentru excludere mutuală (en., mutual exclusion locks)
• Variabile condiție (en., conditional variables)
• Atribute obiect
Crearea firelor

• https://man7.org/linux/man-pages/man3/pthread_create.3.html
• https://man7.org/linux/man-pages/man3/pthread_join.3.html
Primitive de sincronizare
• Accesul la variabilele partajate trebuie controlat pentru a înlătura situațiile de concurență
(en., race conditions) astfel încât să se asigure rularea semantic/logic secvențială și în
consecință determinismul programului.
/*fiecrae fir încearcă să modifice variabila best_cost astfel*/
if (my_cost < best_cost)
best_cost = my_cost;
- Presupunem că avem două fire de execuție
- Valoarea înițială pentru best_cost este 100.
- Valorile pentru my_cost sunt 50 și 75 în firele t1 și t2, respectiv.
- Dacă ambele fire execută if-ul concurent atunci amandouă vor executa ramura then. În
funcție de care fir se va executa primul valoarea best_cost va fi fie 50, fie 75.
- Problemă importantă:
- Natura nedeterministă a rezultatului! Rulări diferite pe acelasi input produc rezultate diferite! ☹
Lacăte (en. locks) pentru excludere mutuală
• O variabila specială denumită mutex poate fi folosită pentru a
gestiona secțiunile critice dintr-un program
• Un fir de execuție dobândește lacătul înainte să execute secțiunea critică și îl
eliberează după ce execută secțiunea critică.
• Dacă lacătul este deja în proprietatea altui fir, atunci firul se blochează până
când lacatul este eliberat.
• Lacătul reprezintă modalitatea prin care se realizează rularea
secvențială, astfel încât utilizarea unui număr prea mare conduce la
degradarea performanței.
Lacăte pentru excludere mutuală (cont.)

Funcția pthread_mutex_lock încearcă să dobândească lacătul mutex_lock. Daca acesta este


în proprietatea altui fie (este închis) atunci firul apelant se blochează. În caz contrar, firul
apelant blochează/închide lacătul si apoi rulează. Rularea cu succes returnează valoarea 0.
Alte valori indică erori, de exemplu blocarea (en. deadlock).

Funcția pthread_mutex_unlock deschide lacătul astfel încat unul dintre firele blocate care
așteaptă va intra în secțiunea critică prin închiderea lacătului. Determinarea firului care
inchide lacătul și rulează se face cu ajutorul unei politici de planificare (en. schedule policy).
Lacăte pentru excludere mutuală (cont.)

Funcția pthread_mutex_init inițializează mutex_lock în starea deschis.


Atributele sunt specificate prin parametrul lock_attr.

Funcția pthread_mutex_trylock încearcă să închidă lacătul mutex_lock. Dacă


încercarea este reușită atunci funcția returnează 0. Daca lacătul este deja
închis de alt fir atunci apelul returnează EBUSY, ceea ce permite firului să
execute alt cod. Aceasă funcție este mai rapidă decât pthread_mutex_lock.
Variabile condiție (en., conditional variables)
• Se aplică principiile de sincronizare de la cozile de așteptare
• În funcție de rezultatul unei anumite condiții un fir se poate atașa la o
anumită coadă de așteptare
• Mai târziu, alt fir care schimbă rezultatul condiției va trezi toate firele care
așteaptă astfel încât acestea pot lua în calcul continuarea rulării.
• Variabilele condiție sunt întotdeauna asociate cu un lacăt.
API pentru variabilele condiție
Obiecte atribut
• Diverse atribute pot fi asociate cu fire, lacăte și variabile condiție.
• Atributele gestionate de fire sunt:
• Parametrii politicii de planificare (en., schedule policy)
• Dimensiunea stivei
• Diverse stări în cadrul programului
• Atributele lacătelor sunt:
• Normale
• Numai un fir îl poate deține pentru a-l închide
• Daca un fir incearcă să îl dețină de 2 ori atunci programul se blocheaza (en., deadlock).
• Recursive
• Un fir poate închide un lacăt de mai multe ori
• Fiecare închidere incrementează un contor, si fiecare deschidere descrește acel contor
• Un fir poate închide un lacăt numai daca are contorul zero.
• Verificarea erorilor
• Încercarea unui fir de a închide un lacăt pe care îl deține conduce la eroare.
Algoritmi paraleli si
distribuiți
Modele de calcul paralel
+ C++ Threads
Paralelism implicit si explicit
• Greșeală frecventă: Programele paralele sunt dificil de scris în
comparație cu cele secvențiale.
• Totuși, gândirea/abordarea paralelă/distribuită trebuie antrenată și înțeleasă
în detaliu cu avantajele și dezavantajele specifice.
• Limitare majoră: depanarea comportamentului nedefinit (ex., nedeterminist)
care poate apărea mult mai frecvent în programele paralele.
• Paralelism implicit
• Programatorul NU specifică în mod clar modul de paralelizare. Paralelizarea
este realizată automat de compilator sau la rulare.
• Paralelism explicit [mult mai util/important pentru programatori]
• Paralelismul este specifcat în codul sursă prin proiectare, directive complexe
sau utilizarea de biblioteci/sisteme specifice.
Paralelism implicit
• Care din urmatoarele instrucțiuni se pot executa în paralel?
a = 2; b = 3;
Da, cele două instrucțiuni nu depind una de alta.

a = 3; b = a;
Nu, execuția paralelă conduce la nedeterminism sau comportament nedefinit.

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


b[i] = i;
Da, nici o iterație nu depinde de alta.

a = f(x); b = g(x);
Nu putem spune fără să cunoaștem funcțiile f și g.
Se poate întâmpla ca instrucțini din f și g să modifice aceleași variabile.
Paralelism explicit: nivele
• Nivelul hardware
• Prin sistemul și organizarea sa fizică.
• Prin rețeaua de comunicare.

• Organizarea logică
• Modul în care programatorul vede platforma pe care va rula aplicația
paralelă.

• Maparea proceselor pe procesoare


• Acesta este nivelul de execuție.
Modelul PRAM
• Este un model teoretic care permite schimbul de date între procesoare în
timp constant (i.e., fără cost).
• Evident, o mașină PRAM nu se poate construi fizic
• Costul de conectare a celor p procesoare la o memorie de dimensiune m astfel încât
accesele la memorie să nu interfereze este O(p*m), ceea ce reprezintă o valoare
foarte mare pentru valorile lui m din practică.
• Măsuri ale complexității
• Pentru fiecare procesor
• Timpul: numărul de instrucțiuni executate
• Spațiu: numarul de zone de memorie accesate
• Pentru mașina PRAM
• Timpul: timpul maxim de rulare dintre timpii procesoarelor
• Paralelismul: numărul maxim de procesoare active
Modelul PRAM
• Numarul foarte mare de conexiuni ar conduce la dificultatea realizării
sincronizărilor necesare.
• Este cel mai bun model conceptual pentru proiectarea algoritmilor
paraleli eficienți.

Instrucțiune simplă paralelă Pentru fiecare x, PRAM va


desemna un procesor care va
For all x in X do in parallel executa # instruction (x)
# instruction (x)
Categorii de PRAM [în funcție de accesul la memoria partajată]
• Restricții pot fi impuse pentru citirea/scrierea simultană în memoria
partajată
• În funcție de modul în care este administrat accesul simultan avem:
• Exclusive Read, Exclusive Write - EREW PRAM
• Concurrent Read, Exclusive Write - CREW PRAM
• Exclusive Read, Concurrent Write - ERCW PRAM (pentru completitudine)
• Concurrent Read, Concurrent Write - CRCW PRAM
Soluționarea scrierii concurente
• Existența citirilor concurente nu crează probleme în logica programelor.
• Scrierea concurentă a două sau mai multe procese în aceeași zonă de
memorie necesită sincronizare/arbitraj; printre metodele de rezolvare a
acestei probleme avem:
• Valoare comună : toate procesele trebuie să scrie aceeași valoare.
• Arbitrar: scrierile sunt efectuate arbitrar.
• Prioritizat: procesul cu prioritate mai mare urmează.
• Sumă: se scrie suma valorilor trimise de procese.
EREW => CREW =>Valoare comună => Arbitrar => Prioritizat
Scenariu cel mai realist ……………………………..> Scenariu cel mai puțin realist
PRAM: Exemplul 1. Determinarea sumei
prefixelor
• Input: o mulțime ordonată de elemente A = {a0, a1, …, an-1}
• Output: mulțimea ordonată {a0, (a0 + a1), …, (a0 + a1 +…+ an-1)}
• Exemplu:
• Input: {4, 3, 5, 1, 8, 11, 2, 9}
• Output: {4, 7, 12, 13, 21, 32, 34, 43}
• Rezolvarea secvențială necesită O(n)
PRAM: Exemplul 1. Determinarea parallelă a
sumei prefixelor
• Soluția secvențială care folosește arbori binari
• Pentru fiecare nod intern calculam valoarea ca fiind suma valorilor tuturor
frunzelor subarborelul corespunzător.
• Abordarea este bottom-up.
• Formularea recursivă este sum[v] = sum [left[v]] + sum [right[v]]
• Putem folosi un array/vector pentru valori. Care este complexitatea?
43
13 30
7 6 19 11
4 3 5 1 8 11 2 9
PRAM: Exemplul 1. Determinarea parallelă a
sumei prefixelor
• Abordare paralelă:

4 3 5 1 8 11 2 9
7 (p0) 6 (p1) 19 (p2) 11 (p3)
13 (p0) 30 (p2)
43 (p0)

Abordarea paralelă rezolvă problema în O(log2(n))


Calcul în timp constant O(1)
• Arhimede: ”Give me a lever long enough
and a place to stand and I will move the
earth.”

• În zilele noastre ... Dacă am avea la


dispoziție o mașină paralelă cu suficent de
multe procesoare am putea determina
valoarea cea mai mică în timp constant.
Provocările algoritmilor paraleli
• Paralelizarea soluțiilor secvențiale reprezintă o provocare algoritmică.
• Scop: Dezvoltarea gândirii paralele!

• Exemplu: Determinarea sumei a n numere.


• Cum influențează numarul de procesoare algoritmul paralel?
• Cum influențează costul comunicării soluția paralelă?
Modelele algoritmice
• Oferă o bază comună pentru dezvoltarea și compararea algoritmilor
paraleli.
• In general, folosesc modelul arhitectural al unei mașini paralele (i.e.,
multiprocesor) cu memorie partajată.
• Memoria partajată reprezintă o abstractizare utilă din punctul de
vedere al programatorului, mai ales în fazele inițiale ale proeictării
soluției.
Modelul cu paralelizare a datelor
• Principiu
• Datele de intrare sunt împărțite între procesoare.
• Se procesează în paralel segmente de date diferite.
• Comunicarea se rezumă la informații marginale.
• Proprietăți
• Include paralelismul în buclă (en., loop paralelism).
• Foarte adecvat pentru mașinile SIMD.
• Comunicația este de obicei implicită (i.e., nu necesită apeluri specifice).
Modelul de paralelizare cu grafuri
• Se descompune algoritmul în mai multe secțiuni distincte.
• Secțiunile se atribuie la procesoare diferite.
• Se folosește fork() / join() / spawn()
• fork() : Crează un nou fir, denumit fir copil.
• join() : Întrerupe firul curent până când alt fir își termină execuția.
• spawn(): Crează și execută un nou fir.
• De obicei, nu conduce la un nivel/grad înalt de paralelizare
• Ajută doar la executarea în oridea corectă a task-urilor.
• Asignarea task-urilor este realizată dynamic.
Modelul de paralelizare • De obicei, fiecare task are asociată o cantitate mică de
date.
bazat pe pool • Pool-ul de task-uri (ex., coadă de prioritate, tabelă de
dispersie, arbore) poate fi centralizat sau distribuit.
Algoritm paralel vs. Formulare paralelă
• Formularea paralelă
• Se referă la paralelizarea algoritmului secvențial.
• Algoritmul paralel
• Poate fi un algoritm complet diferit de cel secvențial.
• Scopul este să definim corect formulări paralele
• Putem avea algoritmi paraleli care nu sunt obținuți pe baza celor secvențiali.
• Algorim/formulare paralelă
• Task = procesare care poate fi efectuată concurent/independent.
• Procese vs. Procesoare = asignarea task-urilor pe procesoare
• Distribuția I/O și a rezultatelor intermediare pe procesoare diferite
• Administrarea accesului la datele partajate (input sau intermediare)
• Sincronizarea procesoarelor la diferite momente ale execuției paralele
• Scop: Maximizarea concurenței și reducerea costurilor suplimentare datorate
paralelizării => Potențial maxim de accelerare.
Procese vs. Procesoare
• Process = Cod/aplicație care execută un task.
• Procesor = HW care execută fizic calculele.
• De obicei, exista o corespondență 1:1 între procese și procesoare.
• Pentru a obține o accelerare față de versiunea secvențială, programul
paralel trebuie să aibă mai multe procese active simultan, care să
rezolve task-uri distincte.
Determinarea procesărilor care se pot
desfășura concurent
• Descompunerea
• Este procesul prin care se împarte toată procesarea necesară în bucăți mai
mici, adică task-uri.
• Task-urile sunt definite de programator si sunt considerate atomice
(i.e., indivizibile).
• Granularitatea unui task se referă la dimensiunea acestuia
• Ex. A[NxN] x b[Nx1] = y [Nx1]
• Granularitate mică => N task-uri, cate unul pentru fiecare element din y
• Granularitate medie => N/3 task-uri, fiecare task procesează 3 elemente din y
Graful de dependințe între task-uri
• Procesarea este văzută ca un graf direcționat aciclic (DAG).
• Fiecare nod din graf este un task.
• O muchie din graf reprezintă o relație de dependență.
• A->B se interpretează ca task-ul A trebuie să se execute înainte de task-ul B,
sau task-ul B necesită terminarea task-ului A.
Metode de descompunere
• Descompunerea datelor
• Descompunerea task-urilor
• Descompunerea recursivă
• Pentru problemele care folosesc abordarea divide et impera
• Fiecare din subproblemele generate de pasul divide devine un task.
• Care este concurența medie? Care este lungimea căii critice?
• Exemple: quicksort, determinarea minimului, etc.
• Descompunerea exploratorie
• Descompunerea speculativă
• Descompunerea hibridă
Descompunerea recursivă
• Cât de bune sunt descompunerile pe care le realizează?
• Care este concurența medie?
• Care este calea critică?

• Cum putem măsura calitatea descompunerii pentru quicksort sau


determinarea minimului?
Descompunerea datelor
• Se folosește pentru obținerea concurenței la problemele care au ca
input o cantitate mare de date.
• Ideea este să definim task-urile având în vedere cantitatea de date.
• Descompunerea se realizează în 2 pași:
• Partiționarea datelor
• Realizarea partiționării procesării pe baza partiționării datelor
• Ce date ar trebui să partiționăm?
• Input/output/rezultate intermediare?
• Cum obținem/definim task-urile necesare?
• Regula este că task-ul care deține datele realizează procesarea.
Exemplu: înmulțirea a două matrici
• Partițonarea datelor de ieșire
A[2x2] x B[2x2] = C[2x2]
• Task 1: C11 = A11*B11 + A12*B21
• Task 2: C12 = A12*B12 + A12*B22
• Task 3: C21 = A21*B11 + A22*B21
• Task 4: C22 = A22*B12 + A22*B22
Descompunerea datelor și descompunerea
exploratorie
Descompunerea datelor: Este cea mai folosită tehnică
• Procesarea paralelă se aplică în multe situații în care dimensiunea datelor de
intrare este mare.
• Împărțirea procesării be baza datelor este un mod natural de a obține un grad
ridicat de concurență.

Descompunerea exploratorie
• Se folosește atunci când descompunem/împărțim spațiul soluțiilor în mulțimi
disjuncte și pentru fiecare muțime creăm câte un Task.
• Poate fi folosită pentru probleme specifice (ex., problema puzzel-ului cu 15
piese, https://en.wikipedia.org/wiki/15_puzzle)
• Poate prezenta anomalii ale accelerării: accelerare supraliniară
Descompunerea speculativă
• Se folosește pentru a obține concurența atunci când următorul *pas*
este unul din multe posibilități și se poate determina numai după ce
task-ul curent se termină.
• Acest tip de descompunere presupune un anumit rezultat/răspuns al
task-ului curent și execută câțiva din pașii următori.
• Mecanismul este asemănător cu execuția speculativă la nivel de
microprocesor.
• Dacă predicțiile sunt greșite
• Rezultatele execuției speculative devin inutile și sunt pierdute/aruncate
• Totuși, se poate întâmpla ca aceasta să fie singura modalitate de a
obține concurența.
Alocarea task-urilor
• De ce este important modul în care alocăm task-urile?
• Nu putem să alocăm aleator tak-urile procesoarelor disponibile?
• Alocarea este o operație critică ce trebuie să minimizeze costurile
suplimentare (en., overhead) care apar în urma paralelizării.
• To = p*Tp + Ts // Ts = timpul de execuție serială, Tp este timpul de execuție
// paralelă pe p procesoare
• Surse ale costurilor suplimentare
• Încărcarea dezechilibrată a procesoarelor
• Comunicarea între procese
• Coordonare/sincronizare/partajarea datelor
Alocarea task-urilor
• Alocările eficiente încearcă să:
• Maximizeze concurența prin alocarea de task-uri independente la procesoare
diferite.
• Minimizeze tipul total de execuție prin asigurarea faptului că task-urile care
se găsesc pe calea critică sunt cele care sunt executate cât mai devreme
posibil.
• Cum putem determina calea critică?
• Aloce task-urile care au grad mare de interacține între ele aceluiași proces.
• Cum putem determina mulțimile de task-uri care au grad mare de interacțiune între ele?
• Ce algoritmi din teoria grafurilor (nod – task; muchie = interacțiune) putem folosi?
Dificultatea alocării task-urilor
• Trebuie avute în vedere dependințele dintre task-uri și graful de
interacțiuni/dependințe dintre task-uri
• Sunt task-urile disponibile apriori?
• Crearea/generarea task-urior poate fi statică sau dinamică.
• Care sunt cerințele task-urilor în ceea ce privește necesitățile de calcul?
• Sunt ele uniforme? ... Aceleași pentru toate task-urile?
• Sunt ele cunoscute apriori?
• Ce cantitate de date este asociată cu fiecare task?
• Care sunt șabloanele de intaracțiune dintre task-uri?
• Statice sau dinamice?
• Le cunoaștem apriori?
• Depind de datele prelucrate?
• Sunt read-only sau read-write?
Tehnici de alocare pentru echilibrarea
încărcării
• Alocare statică
• Task-urile sunt alocate procesoarelor înainte de execuție.
• Se aplică task-urilor care sunt generate static sau au necesități de calcul
cunoscute sau uniforme.
• Este potrivită pentru algoritmii care folosesc descompunerea datelor sau
prelucrează date (input/date intermediare/output) sub forma tablourilor.
• Alocare dinamică
• Task-urile sunt distribuite procesoarelor în timul execuției programului
• Se aplică task-urilor care
• Sunt generate dinamic
• Nu li se conoaște apriori necesitatea de calcul
Metode de echilibrare dinamică a încărcării
• Metode centralizate
• Anumite procesoare sunt responsabile pentru procesări
• Modelul client-server
• Metode distribuite
• Încărcarea poate fi transferată de la un procesor la altul
• Probleme
• Cum detrminăm perechile de procesoare care își deleagă încărcarea
• Cine inițiază transferul? push vs.pull
• Cat de multă încărcare este transferată?
Alocările trebuie să minimizeze costurile
suplimentare generate de interacțiuni
• Maximizarea localizării datelor
• Minimizarea volumului de date schimbat
• Minimizarea frecvenței interacțiunilor
• Minimizarea disputelor
• Suprapunerea interacțiunilor cu efectuarea de calcule
• Replicarea selectivă a datelor și calculelor
Obs: Obținerea mecanismului optim se realizează iterativ și depinde de
interacțiunea dintre descompunere și alocare.
Modele de calcul paralel
• Cu memorie partajată
• Cu fire de execuție
• Cu transmitere de mesaje
• Cu paralelizarea datelor
• Hibride

Aceste modele de calcul paralel sunt abstracte și nu iau în calcul arhitectura


HW a sistemului. Teoretic, acestea pot fi implementate pe orice mașină
fizică.
Alegerea modelului este de obicei o combinație între ceea ce avem la
dispoziție ca HW si alegerea programatorului având în vedere problema
propusă.
Modelul cu fire de execuție
• În acest model un singur proces poate avea mai multe căi de execuție
concurentă.
• Exemplu: Un program principal care include mai multe subprograme.
• Programul principal este lansat în execuție.
• El crează mai multe task-uri (fire de execuție) care sunt programate să fie
rulate de SO în mod concurent.
• Fiecare fir are propriile date locale, dar partajează și rezursele programului
principal. Alest lucru diminuează costurile suplimentare (en., overhead) cu
replicarea resurselor programului principal pentru fiecare fir de execuție.
Fiecare fir beneficiază de acces la memoria globală deoarece partajază spațiul
de memorie al programului principal.
Modelul cu fire de execuție (cont.)
• Ceea ce procesează un fir poate fi înțeles ca un subprogram în cadrul
programului principal. Orice fir poate executa orice subprogram în același
timp cu celelalte fire de execuție.
• Firele comunică între ele cu ajutorul memoriei globale, pe care o modifică în
mod concurent. Acest mode de lucru necesită sincronizare astfel încât
niciodată sa nu avem două sau mai multe fire cu acces simultan la acceeasi
zonă de memorie (i.e., variabilă).
• Firele se pot crea sau distruge, dar programul principal este cel care asigură
resursele necesare până la terminarea aplicației.
• Firele de execuție sunt de obicei asociate cu arhitecturile cu memorie
partajată și sistemele de operare.
Modelul cu fire de execuție (cont.)
• Din punct de vedere al implementării utilizarea firelor presupune:
• O bibliotecă cu care este utilizată de programul client paralel.
• O mulțime de directive pentru compilator care sunt apelate în programul
client.
• Obs: În ambele cazuri programarorul este cel care determină și administrează
tot paralelismul.
• Implementările paralele cu fire de execuție sunt printre primele
disponibile. Eforturile de standardizare s-au concretizat în două
implementări ale firelor: POSIX Threads și OpenMP.
Modelul cu transmitere de mesaje
• Transmiterea de mesaje este printre cele mai utilizate modele de
programare paralelă.
• Programele care folosesc transmiterea de mesaje:
• Crează mai multe task-uri
• Fiecare task are alocat un set de date
• Fiecare task este unic identificat
• Task-urile interacționeză prin trimiterea/primirea de mesaje către/de la alte
task-uri.
• Nu exclud crearea dinamică a task-urilor, execuția mai multor task-uri pe un
singur procesor, sau execuția de programe diferite de către task-uri diferite.
Modelul cu transmitere de mesaje (cont.)
• In practică, de cele mai multe ori:
• programul crează un număr fix de task-uri identice la inițializare și nu permite
crearea sau distrugerea de task-uri în timpul execuției.
• Acest model implentează modeul SIMD (Single Program Multiple Data)
deoarece fiecare task execută același cod (aceleași instrucțiuni) dar pe date
diferite.
• Mai multe task-uri se pot găsi pe aceeași mașină fizică, dar se pot afla și pe
mașini diferite.
• Transferul de date necesită ca fiecare proces să execute operații în mod
coordonat cu altele. De exemplu, o operație send trebuie să aibă o operație
pereche receive bine determinată. Sisteme mai avansate implementează
listener pattern (vezi Inginerie software, șabloane de proiectare).
Modelul cu transmitere de mesaje (cont)
• Din punct de vedere al implementării trebuie utilizată o bibliotecă ce
conține toate subprogramele care se folosesc în codul sursă.
Programatorul este cel care stabilește întreg modul de paralelizare.
• MPI : lansat în 1994
• MPI-2 : lansat în 1996
• MPI 4.0 lansat în 2020
https://www.mpi-forum.org/
Modelul cu paralelizarea datelor
• Acest model valorifică o concurență de se obține și aplicarea aceleiași
operații mai multor elemente dintr-o structură de date de tip tablou.
• O mulțime de task-uri vor lucra simultan și vor coopera pe aceeași
structură de date, dar fiecare task va lucra pe o porțiune diferită de
date față de celelalte task-uri.
• Exemple:
• Adaugă 2 la toate elementele din tabloului
• Crește salariul angajaților cu vechime de peste 5 ani
• Obs: Un program care implementează paralelizarea datelor constă dintr-o
secvență de astfel de operații.
Modelul cu paralelizarea datelor (cont.)
• Compilatorul trebuie să fie informat de programator referitor la modul în care
datele sunt distribuite către procesoare (i.e., cum sunt partiționate datele pe
task-uri). Ulterior, compilatorul poate genera automat codul de comunicare.
• Pe sistemele cu memorie partajată, toate task-urile pot avea acces la structura de
date prin memoria globală.
• Pe sistemele cu memorie distribuită, structura de date este împărțită și este
disponibilă în bucăți (en., chunks) care se găsesc în memoria locală a fiecărui task.
• Distribuția poate avea în vedere si replicarea redundantă.
• Implementările cu memorie distribuită se bazează pe compilator pentru a converti
programul în cod cu apeluri de bibliotecă (ex., MPI) pentru a distribui datele către
procese. Transferul de date este realizat transparent pentru programator.
Modelul cu memorie partajată
• În acest model, task-urile partajază un spațiu de adrese de memorie
comun la care au acces (i.e., citire și scriere) în mod asincron.
• Mecanismele de tipul lacăte/semafoare trebuie folosite pentru
gestionarea accesuli la memoria partajată.
• Un avantaj pentru programator este faptul că un există noțiunea de
*proprietar* al datelor. Acest aspect conduce la faptul ca nu este nevoie să
specificăm explicit modul de cumunicare dintre producător și consumator.
• Acest model simplifică dezvoltarea aplicațiilor. Totuși, înțelegerea și
administrarea valorilor datelor poate deveni dificilă pe cele mai multe
arhitecturi cu memorie partajată.
Modelul cu memorie partajată
• Pe platformele cu memorie pargajată, compilatoarele translatează
variabilele din program în adrese de memorie efective care sunt
globale.
• Este dificil de implementat un sistem cu memorie distribuită
(memoria este fizic distribuită) care să funcționeze ca un sistem cu
memorie partajată.
Modelul hibrid
• Două sau mai multe modele anterioare pot fi combinate.
• Un exemplu uzual este combinația dintre modelul cu transmitere de
mesaje (MPI) cu modelul cu fire de execuție (PThreads) sau cel cu
memorie partajată (OpenMP).
• Un alt exemplu comun este cel care combină modelul cu paralelizarea
datelor cu cel cu transmitere de mesaje.
• Implementările cu paralelizarea datelor pe arhitecturile cu memorie
distribuită folosesc modelul cu transmitere de mesaje pentru a transfera date
între task-uri în mod transparent pentru programator.
Fire de execuție în C++
• Programarea concurentă a devenit o abilitate esențială pentru toți
programatorii C++ deoarece cele mai multe sisteme au mai multe
procesoare.
• Utilizarea eficientă a masinilor multicore necesită abilitatea de codare a
aplicațiilor concurente.
• C++ 11 introduce concutența, firele de execuție, variabilele
condiționale, mutex, etc.
• Standardul C++11 se schimba tramatic cu C++ 17: apare STL parallel.
• C++ implementează concurența prin multithreading și paralelism.
Fire de execuție în C++
• C++ multithreading
std::thread thread object care poate fi creat și folosit pentru a rula task-uri în mod independent
• Odată creat, firului ii este transmisa o funcție care să se execute și eventual
parametrii pentru acea funcție, care poate fi:
• Un pointer la o funcție (en., function pointer)
• Un obiect funcție (en., function object)
• O expresie lambda
• Deoarece fiecare fir poate executa doar o funcție la un moment dat, putem
avea bazine (en., pools) de fire care să ne perimită reutilizarea lor
asemănător ca într-un sistem multitasking nelimitat.
• Astfel, se profită de existența mai multor procesoare și se permite determinarea
nivelului la care aplicația doreste să utilizeze resursele sistemului.
Fire de execuție în C++ (comparație cu
PThreads)
• Comparativ cu Pthreads, în C++ beneficiem (sau trebuie să avem în
vedere) de:
• Ciclul de viață al obiectelor și tratarea excepțiilor
• Nu avem o funcție pthread_cancel
• Clasele pentru mutex, variabile condiție și lacăte (en., locks) folosesc tehnica
RAII (resource acquisition is initialization,
http://www.stroustrup.com/bs_faq2.html#finally )
• RAII reprezintă un aspect principal oferit de biblitecă prin proiectare
• Avem la dispoziție mecanisme pentru crearea de obiecte apelabile (en,
callable objects) și expresii lambda (i.e, funcții anonime) care sunt integrate
facilitățile oferite de firele de execuție.
Fire de execuție în C++
void print(int n, const std::string &str) {
std::cout << „Int: " << n << std::endl;
std::cout << „String: " << str << std::endl;
}

int main() {
std::thread t1(print, 5, „One thread");
t1.join(); //se oprește rularea programului principal până la
// terminarea firului t1
return 0;
}
Fire de execuție în C++
• Multithreading

std::vector<std::thread> threads;
for (int i = 0; i < s.size(); i++) {
threads.push_back(std::thread(print, i, s[i]));
}

for (auto &th : threads) {


th.join();
}
Fire de execuție în C++
• ”Funcția firului”: Funcția executată de firul de execuție nu reprezintă ceva special,
numai că aceasta nu trebuie să se termne prin aruncarea unei excepții. În această
situație se va apela automat std::terminate.
• https://en.cppreference.com/w/cpp/error/terminate
• Tipuri de lacăte, mutex-uri, strategii
• std::recursive_mutex, std::timed_mutex; std::unique_lock; std::defer_lock, std::try_to_lock
• Variabile condiție
• std::future
• Tipuri atomice și funcții pe tipurile atomice
• Referințe rvalue, care permit perfect forwarding
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm )
pentru o funcția firului.
• Suport adițional pentru functori (i.e., function objects) prin std::function și
std::bind
Fire de execuție în C++: Greșeli frecvente
• Lipsa apelului join înainte de terminarea programului determină crash. La finalul
programului firul va ieși din scop și astfel va fi apelat destructorul, care va apela
std::terminate.
• Incercarea de a apela join pe un fir la care s-a apelat detach.
• Soluția este ca înainte de join sa verificam dacă firul este joinable.
• Trebuie înțeles că apelul std::thread::join() are ca efect blocarea firului apelant (adică, a
programului principal ... de obicei)!
• Transmiterea parametrilor catre funcția firului nu este implicit prin referință, ci prin
valoare!
• Lipsa protejării datelor partajate într-o secțiune critică cu ajutorul mutex.
• Lipsa deschiderii lacătelor la ieșirea din secțiunea critică.
• Secțiunile critice trebuie păstrate cât mai mici și compacte.
• Gestiunea proastă a mai multor lacăte: ordinea de închidere/deschidere trebuie atent
administrată dacă avem mai multe fire si mai multe lacăte.
Fire de execuție în C++: Greșeli frecvente
• Încercarea de a dobândi std::mutex de două ori poate conduce la
comportament nedefinit.
• Nefolosirea std::atomic pentru tipuri de date simple (ex., bool, int).
Folosirea mutex în aceste situații conduce la degradarea performanțelor.
• Crearea și distrugerea unui număr mare de fire atunci când avem la
dispoziție un thread pool.
• Lipsa tratării excepțiilor pentru firele care rulează în background.
• Folosirea firelor pentru simularea task-urilor asincrone. Folosiți std::async.
• Crearea mai multor fire decat numărul de procesoare disponibile.
Algoritmi paraleli și distribuți
Masurarea performanțelor sistemelor paralele
+ Parallel STL
Metrici pentru sistemele și aplicațiile paralele
• Timp de execuție (en., Execution Time)
• Accelerare (en., Speedup)
• Eficiență
• Cantitatea de date procesată (en., System Throughput)
• Costul
• Granularitatea
• Scalabilitatea
• Gradul de concurență (i.e, paralelizare, utilizare)
Evaluarea programelor secvemțiale și
paralele
• Algoritmii secvențiali sunt evaluați din punct de vedere al:
• Eficienței algoritmice: aceasta este o măsură teoretică
• Performanței: aceasta se referă la timpul de rulare si depinde de
implementare
• Obs: eficiența nu depinde de un anumit sistem
• Timpul de rulare a unui program paralel depinde și de alți factori:
numărul de procesoare, modul de comunicare din sistemul pe care se
rulează.
• Obs: Algoritmii paraleli trebuie anaizați în contextul platformei (HW, SO, CPU
(număr de procesoare), GPU) pe care rulează.
• Un sistem paralel este combinația dintre algoritmul paralel, implementarea
acestuia și platforma pe care se execută aplicația.
Despre performanță
• Timpul ” Wall clock” (i.e., timpul real, timpul total necesar) este timpul
măsurat de la începutul primului procesor până când se termină rularea pe
ultimul procesor.
• Notație: Ts este timpul rulării secvențiale; Tp este timpul necesar rulării paralele
• Accelerarea (en., speedup): Cu cât este mai rapidă varianta paralelă?
• Care este versiunea de bază (en., baseline) cu care comparăm varianta paralelă?
Cea mai bună? Medie?
• Putem folosi o variantă suboptimală secvențială?
• Granularitatea
• Mică = număr mare de task-uri simple
• Mare = număr mic de task-uri complexe
Despre performanță (cont.)
• Gradul de concurență este numărul maxim de task-uri care pot fi
executate simultan.
• Gradul mediu de concurență este numărul mediu de task-uri care pot
fi executate în paralel pe întreaga rulare a programului.
• Gradul de concurență crește pe măsură ce descompunerea este mai fină.
• Calea critică este costul cel mai mare pe care îl poate atinge un drum
de la start la finish în graful de dependințe al task-urilor.
• Costul căii este suma ponderilor nodurilor.
• Graful de interacțiune al task-urilor
• Un nod corespunde unui task, iar o muchie conectează două noduri daca
task-urile respective cumunică/interacționază unul cu altul.
Costuri suplimentare (en., overheads)
• Dacă avem la dispoziție două procesoare trebuie ca varianta paralelă
să ruleze de două ori mai repede?
• NU! Deoarece apar costuri suplimentare:
• Calcule inutile
• Așteptare/pauză (en., idle)
• Timp necesar comunicației
• Diverse dispute (ex., data race)
... care conduc la degradarea performanței.
Timpul de execuție și costurile suplimentare
• Timpul de execuție/răspuns
măsoară intervalul de timp scurs
între trimiterea unei cereri până
când este primit raspunsul.
• To = costul suplimentar (en.,
overhead) pentru varianta paralelă
To = p*Tp – Ts, unde Tp si Ts sunt
timpii pentru execuția paralelă și
secvențială.
Execuția unui program paralel pe 8
procesoare. Observați stările unui
procesor: rulare, comunicare, pauză.
Minimizarea costurilor suplimentare
• Sursele tradiționale ale costurilor suplimentare sunt:
• Apelurile de funcții: necesită lucrul cu stiva, apar ramuri ale programului.
• Recursivitate
• Pentru soluții algoritmice diferite ale aceleiași probleme putem avea
costuri suplimentare de paralelizare diferite
• Costurile suplimentare necesare implementării unei anumite soluții
algoritmice pot decide dacă are rost sau nu să paralelizăm
algoritmul/funcția respectivă.
Surse ale costurilor suplimentare
• Comunicarea între procese apare atunci când este necesară
transmiterea de date/rezultate de la un procesor la altul.
• Așteptarea/pauza (en., idle) poate apărea la un proces din cauza
lipsei balansării încarcării (en., load balancing) sau a sincronizărilor
necesare.
• Calculele în plus (en., excess computation) sunt acele calcule care nu
apar în varianta secvențială.
• Acestea sunt multe atunci când algoritmul este dificil de paralelizat sau când
anumite calcule sunt efectuate pe mai multe procesoare pentru a diminua
comunicarea.
Accelerarea (en., speedup)
• Acelerarea este cea mai utilizată măsură a performanței versiunii paralele a
unui algoritm secvențial.
• Ts este cel mai bun timp secvențial.
• Tp este timpul necesar rulării versiunii paralele pe p procesoare.
• Accelerarea = Ts / Tp
• Accelerarea liniară apare când Tp = Ts / p, ceea ce reprezintă cazul ideal.
• Cum se determină Ts?
• Pe un procesor dintr-un sistem paralel?
• Folosind cel mai bun sistem secvențial disponibil?
• Rulând un algoritm paralel pe un singur procesor?
Accelerarea: altă definiție/abordare
• Accelerarea este timpul necesar unui algoritm paralel să ruleze pe un procesor
împărțit la timpul necesar algoritmului paralel sa ruleze pe N procesoare.
• Această abordare este înșelătoare deoarece multi algoritmi paraleli folosesc operații
adiționale (ex., comunicarea) pentru a obține paralelismul.
• Aceasta abordare marește pe Ts, și astfel exagerează accelerarea determinată.
• Exemplu: Bubble sort
Ts = 150 s
Tp = 30 s ;// pentru o variantă paralelă eficientă
Accelerarea = 150 / 30 = 5. Este corect?
Dacă un quicksort serial durează numai 20 s?
Atunci accelerarea = 20/30 = 0.67 ☹ Aceasta este o evaluare mai realistă!
Limitele accelerării
• Accelerarea poate fi 0, adică programul paralel nu se termină
niciodată.
• Accelerarea este limitată de p, numărul de procesoare.
• O accelerare mai mare decât p este posibilă numai dacă fiecare
element de procesare (ex., procesor) petrece mai puțin de Ts/p timp
pentru rezolvarea problemei.
• În această situație, un singur procesor ar putea fi folosit pentru a obține un
program secvențial mai rapid, ceea ce contrazice presupunerea că avem la
dispoziție un program secvențial optim ca referință (en., baseline).
Factori care limitează accelerarea
• Costurile suplimentare de calcul (software)
• Chiar dacă avem la dispozișie un algoritm paralel corect și somplet echivalent,
costurile suplimentare apar de obicei din cauza implementării concurente.
• Echilibrarea proastă a încărcării
• Accelerarea este de obiei limitată de viteza celui mai încet nod. Astfel, un
aspect esențial se referă la asigurarea încărcării egale a nodurilor (ex.,
procesoare) cu același efort de calcul.
• Costurile suplimentare datorate comunicării
• Presupunând ca procesarea și comunicarea nu pot fi suprapuse (i.e.,
procesăm apoi transmitem rezultatul) putem spune că timpul necesar
comunicării de date între procesoare degradează în mod direct accelerarea.
Factori care limitează accelerarea (cont.)
• Cod care este nativ serial
• Dacă dorim să paralelizăm o aplicație, dar asta nu înseamnă că trebui ca tot
codul să proprietatea că poate fi paralelizat.
• Algoritmi care nu sunt optimi
• Optimalitatea algoritmului proiectat crează premisele unei implementări
performante.
• Costurile suplimentare datorate sincronizărilor
• Nu putem paraleliza un algoritm fără să fie nevoie de sincrinizare.
Accelerarea liniară
• Accelerarea liniară reprezintă dezideratul paralelizării. Astfel, folosind
N procesoare, o accelerare de N este considerată excelentă.
• În practică, accelerarea este mai mică decât N.
• Pentru aplicațiile care scalează bine accelerarea ar trebui să crească
proporțional cu numărul de procesoare.
• Când spunem ca o aplicație scalează?
• Accelerarea supra-liniară apare atunci când:
• Sunt folosite valori injuste pentru Ts
• Apar diferențe în ceea ce privește natura HW folosit.
Accelerarea supra-liniară bazată pe resurse
• Dacă avem la dispoziție lățime de bandă mai mare pentru accesul la
cache atunci putem obține accelerare supra-liniară.
• Exemplu:
• Un procesor cu 64 KB memorie cache face ca în 80% din situații datele
necesare să se afle în cache.
• Dacă sunt folosite două procesoare, atunci în 90% din situații datele necesare
se află în cache.
• Din cele 10% rămase, 8% se gasesc în memoria locală iar 2% sunt la distanță.
• Dacă timpii de acces la cache, memoria locală si la distanță sunt de 2ns, 100ns
și 400ns, atunci accelerarea va fi de 2.43!
Eficiența
• Accelerarea nu măsoară/determină/stabilește cât de eficient sunt
utilizate procesoarele.
• Merită sa folosim 100 de procesoare pentru o accelerare de 2?
• Eficiența este definită ca raportul dintre accelerare și numărul de
procesoare necesar pentru a obține acea accelerare.
e = accelerarea / numarul de procesoare = s / p
• Eficiența este maxim 1 și reprezintă fracțiunea de timp în care un
procesor este folosit la maxim.
• Ideal, daca s = p atinci e =1.
Redundanța
• Redundanța HW: mai multe procesoare sunt folosite pentru o singură
aplicație, dintre care cel puțin unul nu rulează nimic (i.e., standby)
• Crește costurile dar, de multe ori, este o soluție practică pentru evitarea întreruperii
rulării aplicației
• Sistemele dedicate au căi de comunicație redundante. Astfel, chiar dacă o cale de
comunicație (i.e., magistrală) este distrusă fizic totuși aplicația va continua să ruleze.
În această situație ne îndreptăm către un sistem distribuit!
• Redundanța poate fi implementată la nivele diferite
• Servere individuale pot fi replicate
• HW-ul redundant poate fi folosit pentru activități cu prioritate mai scăzută atunci
când nu sunt detectate defecte
• Redundanța SW: proiectarea sw trebuie realizată astfel încât starea
aplicației să poată fi recuperată atunci când sunt detectate defectări (en.,
faults)
Granularitatea paralelismului
• Este determinată de dimensiunea medie a componentelor
secvențiale care compun calculul paralel
• Paralelismul independent este atunci când procesele sunt
independente și nu este nevoie de sincronizare.
• Paralelismul este
• Slab granularizat (en., coarse-grained) atunci când procesele sunt relativ
independente iar sincronizările sunt ocazionale.
• Mediu granularizat atunci când firele de execuție se sincronizează frecvent.
• Fin granularizat (fine-grained) atunci când este nevoie de sincronizare la
fiecare câteva instrucțiuni.
Granularitatea: efecte asupra performanței
• De obicei, folosirea unui număr mai mic de procesoare crește
performanța.
• Diminuarea scalării (en., scaling down) unui sistem paralel apare atunci când
sunt folosite mai puține procesoare decât maximul posibil.
• Atunci când numărul de procesoare scade cu n/p, timpul procesor
crește la fiecare cu n/p.
• Costul de comunicare nu ar trebui să crească cu această cantitate.
• Aceasta este o posibilă îmbunătățire oferită de administrarea
granularității.
Granularitatea: efecte asupra performanței
• Folosirea unui număr foarte mare de procesoare/fire (granularitate
fină) poate conduce la diminuarea masivă a paralelismului.
• Ex. Înmulțirea unei matrici dense cu un vector nu poate crea mai mult de n2
task-uri.
• Task-urile concurente trebuie uneori să schimbe date cu alte task-uri.
Această situație conduce la costusi suplimentare de comunicație.
Concluzie:
- Compromisul dintre granularitatea descompunerii și costurile
suplimentare asociate determină limitele de performanță ale versiunii
paralele.
Scalarea programelor paralele
• Eficiența unui program paralel este
E = S/p = Ts / (p*Tp) sau E = 1/(1 + Tp/Ts)
• Costurile suplimentare (en., overhead) totale To sunt o funcție
crescătoare dependentă de p
• Pentru o anumită dimensiune a problemei (Ts este constant), pe
masură ce creștem numărul de procesoare va crește și To.
• Astfel, eficiența generală a programului scade!
• Soluție: Trebuie determinată granularitatea optimă pentru problema
abordata. Aceasta se determină experimental în funcție de platforma
pe care rulează aplicația.
Scalarea programelor paralele (cont.)
• Asadar, costurile suplimentare totale To depind atât de dimensiunea
problemei Ts cât și de numărul de procesoare p.
• În multe cazuri, To crește subliniar în raport cu Ts.
• În aceste cazuri, eficiența crește dacă dimensiunea problemei este mărită dar
păstrăm numărul de procesoare constant.
• Sistemele care au aceste proprietăți sunt denumite sosteme paralele
scalabile.
Eficiența (E) în raport cu numarul de
procesoare (p) și dimensiunea problemei (W)
• Cresterea numărului de procesoare descrește performanța pentru o anumită
dimensiune a problemei
• Cresterea dimensiunii problemei poate crește performanța pentru un anunmit
număr de procesoare.
Determinarea scalabilității
• Dimensiunea problemei W este dată de numărul de operații care sunt
efectuate de cel mai bun algoritm secvențial care rezolvă problema.
• Gradul/raportul cu care poate fi crescută dimensiunea problemei în
raport cu numărul de procesoare pentru a păstra eficiența constantă
determină scalabilitatea sistemului.
• Cu cât raportul este mai mic cu atat mai bine.
Variația gradului de concurență/paralelism
• Este dată de numărul de operații care pot fi executate
simultan/paralel.
• Pentru paralelismul de tip pipeline (unde datele au forma unui vector)
gradul este același cu numărul de procesoare.
• Gradul de concurență poate fi constant în timpul rulării algoritmului,
dar de cele mai multe ori variază.
• Metoda cea mai intuitivă pentru reprezentarea prelucrarilor paralele
este cu ajutorul grafurilor computationale (en., computation graphs)
care utilizează grafuri directionate aciclice în care sunt definite
nodurile sursă și destinație.
Alte metrici ale scalabilității
• Sunt impuse de nevoile specifice ale aplicațiilor
• Pentru aplicațiile în timp-real obiectivul este ca scalarea să permită
îndeplinirea task-ului într-o anumită limită de timp.
• Sistemele care au constângeri în ceea ce privește memoria, metricile
iau în modul în care evoluează necesitatea de memorie în raport cu
dimensiunea problemei sau numarul de procesoare folosit
Accelerarea scalată (en., scaled speedup)
• Este acea accelerare care se obține atunci când dimensiunea
problemei este crescută liniar în raport cu numărul de procesoare.
• Dacă accelerarea scalată este liniară atunci considerăm că sistemul
este scalabil.
• Dacă memoria necesară crește liniar în raport cu p, atunci accelerarea
scalată crește dimensiunea problemei până la umplerea memoriei.
• Alternativ, dimensiunea problemei se poate crește până când timpul
de execuție atinge un maxim permis.
Legea lui Amdahl
• Este folosită pentru a detrmina limita superioară a accelerării
• Accelerarea unui algoritm paralel este limitată de procentul de
operații care trebuie efectuate secvențial.
• Legea lui Amdahl presupune că datele de intrare au dimensiune
fixă/constantă și același de operații seriale sunt necesare.
• Dacă timpul necesar calculelor seriale este o fracție σ din timpul total (0< σ <=
1) atunci timpul de calcul paralel este (1- σ)
• Presupunând că accelerarea este liniară
• Tserial = σ * T1
• Tparalel = (1- σ) * T1 / N;// N este numărul de operații
• Accelerarea = 1 / (σ + (1- σ) / N)
Consecințe ale legii lui Amdahl
• Avem un program cu N = 100 de operații, fiecare operație costă o
unuitate de timp.
• Presupunem că σ = 0.2, folosind 80 de procesoare
• Accelerarea = 100/(20+80/100) = 100/20.8 < 5
• O accelerare de 5 este posibilă indiferent de numărul de procesoare
diponibile.
• Concluzie:
• De ce să ne complicăm inutil cu procesarea paralelă? ... Putem să așteptăm
un procesor mai rapid :)
Condecințe ale legii lui Amdahl
• Pentru a obține o accelerare liniară cu 100 de procesoare (i.e,
accelerarea să fie 100) trebuie ca nici un calcul sa nu trebuiască să fie
facut secvențial.
• Pentru a avea o accelerare de 99 cu 100 de procesoare trebuie ca
maxim 0.01% din programul inițial trebuie să se evecute secvențial.
• Care este accelerarea pe care o putem obține cu 100 de procesoare
dara 30% din programul original ramane secvențial?
Accelerarea = 1/(0.7 + 0.7/100) = 1.4
• Concluzie: Partea secvenială trebuie restructurată pentru a permite
paralelizare eficientă.
Limitări ale legii lui Amdahl
• Pentru a evita limitările legii lui Amdahl trebuie să paralelizăm
algoritmii care au parte de prelucrare secvențială mică.
• Legea lui Amdahl nu ia în caclul costurile suplimentare (overheads)
necesare pentru comunicare, sincronizare, administrarea firelor de
execuție.
• Legea lui Amdahl nu ia în caclul dimensiunea problemei.
• De obicei, pe măsură ce numărul de procesoare crește este probabil ca și
dimensiunea datelor procesate să crească.
Legea lui Gustafson (accelerarea scalată)
• Dacă o aplicație paralelă care folosește 32 de procesoare poate prelucra
date de intrare de 32 de ori mai mari decât datele de intare inițiale, crește
și timpul de execuție al parții secvențiale din algoritmul paralel?
• NU, nu crește cu același raport ca și creșterea dimensiunii datelor de intare.
• Exeprimentele arată ca timpul de execuție secvențial va rămâne aproape constant.
• Accelerarea <= p + (1-p)*s, unde p este numărul de procesoare iar s este
procentul de execuție secvențialp dintr=o aplicație paralelă pentru un
anumit input.
• Deoarece procentul de execuție secvențială în cadrul aplicației paralele
este cunoscut, formula poate fi folosită cu succes pentru determinarea
accelerării scalate în raport cu varianta secvențială pentru aceeași
dimensiune a problemei.
Rezultate comparative
• Daca 1% din timpul de execuție pe 32 de procesoare este alocat
execuției seriale, accelerarea pentru aceleași date de intrare rulate pe
un singur procesor cu un singur fir de execuție este:
Accelerarea <= 32 + (1-32)*0.01 = 32 -0.31 = 31.69
• În situația în care procentul de execuție secvențială este de 1% atinci
Legea lui Amhdal spune că:
Accelerarea <= 1/(0.01 + (0.99/32)) = 24.43
• Acesta este un calcul nerealist deoarece procentul de timp secvențial este
relativ în ceea ce privește execuția pe cele 32 de procesoare
Performanța referitoare la memorie
• Capacitatea: De câti bytes este nevoie?
• Lațimea de bandă: Câți bytes se pot transfera pe secundă?
• Latența: De cât timp este nevoie pentru a prelucra/aduce (en., fetch)
un cuvânt?
• Întârzierile de routare: Apar atunci când datele trebuie aduse din
zone diferite de memorie.
• Eventualele dispute se rezolvă prin blocarea memoriei.
STL Parallel
• Biblioteca STL are peste 100 de algorimi pentru căutare, numărare,
etc.
• C++ 17 a adăugat suport pentru 69 de algoritmi paraleli în biblioteca
standard
• MSVC a adaugat suport pentru unii algoritmi
• API-ul descris în standard pentru algoritmii paraleli nu precizează
exact cum este realizată paralelizarea.
• Algoritmii paraleli se bazează în intregime pe implementarea din
bibliotecă și nu necesită nici un ajutor special de la compilator.
STL Parallel

https://www.modernescpp.com/index.php/c-17-new-algorithm-of-the-standard-template-library
STL Parallel: Pași de urmat
1. Găsiți un algoritm pe care doriți sa-l paralelizați în aplicația
secvențială. De exemplu, algorimii care necesită mai mult de O(n)
timp (ex., sort) sunt candidati foarte buni. De asemenea, trebuie ca
aceștia să fie printre cei mai mari consumatori de timp din aplicație.
2. Verificați dacă ceea ce doriți să paralelizați este sigur d.p.d.v. al
consistenței. Paralelizarea trebuie să nu permită comportamente
nedefinite (en., undefined behaviour) sau să conducă la
nedeterminism.
3. Alegeți o politică de execuție paralelă.
STL Parallel: Pași de urmat (cont.)
4. #include <execution> pentru a face politicile de execuție paralelă
disponibile.
5. Adăugați una dintre politicile de execuție paralelă ca prim parametru
al apelului la algoritmului paralel.
6. Rulați/experimentați și realizați un benckmark ar rezultatului pentru
a vă asigura că versiuena paraleă este mai rapidă.
Obs:
- Paraleizarea NU este întotdeauna mai rapidă! Mai ales pentru
iteratorii care nu au acces aleator (en., non-random-access iterators),
atunci când dimensiunea datelor de intare este mică, sau atunci când
paralelismul detrmină blocarea pentru accesul la alte resurse (ex., un
disk).
STL Parallel
• Algoritmii paraleli depind de diponibilitatea paralelismului HW
• Nu trebuie foarte multe procesoare pentru a demonstar accelerarea.
• Mulți algoritmi sunt de tip divide et impera care nu vor scala perfect
cu numărul de fire, dar se vor executa mai repede.
• Algoritmii sunt paralelizabili în siguranță dacă funcțiile de acces ale
elementelor (ex., operașii cu iteratori, predicate, sau alt cod necesar)
respectă regulile de acces la date (i.e, data race), adică putem avea
oricâte citiri ale aceleiași variabile dar nu putem avea decât o scriere.
STL Parallel: politici de execuție
• Standardul include următoarele politici:
Politica secvențială: std::execution::seq
Politica paralelă (dar nu vectorizată): std::execution::par
Politica paralelă și vectorizată: std::execution::par_unseq
Politica vectorizată (dar nu paralelă) [din C++20]: std::execution::unseq
În plus față de cerințele necesare policii paralele, politica paralelă și
vectorială necesită ca funcțiile de acces la elemente să accepte
progresul/rularea aplicației cu constrângeri mai slabe (ex., nu necesită
și nu folosesc lacăte (en., locks) pentru acces la memorie). Mai mult
decât atât, trebuie ca CPU și sistemul de operare să suporte
instrucțiuni SIMD.
Efortul depus pentru paralelizare este minim = adăugarea unui
parametru!
STL Parallel: politici de execuție (cont.)
• Biblioteca poate ignora complet politica de execuție specificată
• Adică poate recurge la execuția serială
• Politica de execuție specificată reprezintă o indicație (en., hint)
pentru bibliotecă referitor la nivelul maxim de
paralelixare/vectorizare acceptat.
• Limitări: Biblioteca realizează paralelizarea algoritmului, NU
concurența!
• Concurența este o problemă de proiectare! VS. Paralelismul adresează
eficiența la rulare.
• Rob Pike, Concurrency Is Not Parallelism,
https://www.youtube.com/watch?v=oV9rvDllKEg
STL Parallel: politici de execuție (cont.)
• Limitări (cont.):
• Nu este permisă/posibilă proiectarea concurentă globală a aplicației.
• Sunt permise doar optimizări locale prin paralelizarea unor apeluri.
• Concluzie gresită: Folosim STL paralel și nu mai trebuie să acordăm atenție
problemelor cauzate de concunență deoarece biblioteca s-a ocupat de acest
lucru!
• Scopul bibliotecii NU este să rezolve probolemele de concurență, ci să rezolve
probleme locale de eficiență.
STL Parallel
• Paralelizare are și costuri suplimentare (en., overhead) și va conduce
la ineficiență (i.e., timp mai mare de execuție decât varianta
secvențială) pentru valori suficient de mici ale lui N (i.e., numarul de
numere pe care le sortăm).
• Atunci când se folosește o politică de execuție în paralel este
responsabilitatea programatorului să evite situațiile de *data race*
sau de blocare (en., deadlocks).
STL Parallel
• În timpul execuției unui algortm paralel care foloseste oricare dintre
politicile de paralelizare, dacă apelarea unei funcții de acces la un
element se finalizează printr-o excepție netratată atunci
std::terminate este apelat automat.
• Implementările client pot defini alte politici de execuție care să
trateze excepțiile în mod proprie/particular.
• Aruncarea (și tratarea excepțiilor) este recomandată!
STL Parallel: Exemplu
• Filtrarea elementelor dintr-un container
• Se dă un container A cu elemente. Scrieți un program care copiază
elementele care corespund unui predicat în containerul B, și returnați
containerul B.
• Funcția filter are urmatoarea semnătură:

auto Filter(const Container& cont, UnaryPredicate p) {}


STL Parallel: Exemplu
//raw loops
template <typename T, typename Pred>
auto FilterRaw(const std::vector<T>& vec, Pred p) {
std::vector<T> out;
for (auto&& elem : vec)
if (p(elem))
out.push_back(elem);
return out;
}
STL Parallel: Exemplu
//variantă paralelă
std::mutex mut;
std::for_each(std::execution::par, begin(vec), end(vec),
[&out, &mut, p](auto&& elem) {
if (p(elem))
{
std::unique_lock lock(mut);
out.push_back(elem);
}
});
https://www.cppstories.com/2021/filter-cpp-containers/
Pentru alte 10 implementări diferite!
Referințe
• https://www.bfilipek.com/2017/08/cpp17-details-parallel.html
• https://www.bfilipek.com/2018/11/parallel-alg-perf.html
• Bryce Adelstein Lelbach, C++ Parallel Algorithms and Beyond, CppCon 2016,
2016, https://www.youtube.com/watch?v=Vck6kzWjY88
• Billy O’Neal, Using C++17 Parallel Algorithms for Better Performance,
Microsoft C++ Team Blog, 2018,
https://devblogs.microsoft.com/cppblog/using-c17-parallel-algorithms-for-
better-performance/
• Lucian Radu Teodorescu, A Case Against Blind Use of C++ Parallel
Algorithms, https://accu.org/journals/overload/29/161/overload161.pdf
Algoritmi paraleli și distribuiți
Arhitecturi de calcul paralel/distribuit
+ Java paralel
Ce este ”arhitectura unui sistem de calcul
paralel”
• Arhitectura unui sistem de calcul paralel trebuie să aibă în vedere
• Alocarea resurselor
• Cât de mare este colecția de date administrată?
• Cât de puternice sunt elementele sistemului?
• Câtă memorie au elementele sistemului?
• Accesul la date, comunicare și sincronizare
• Cum comunică elementele sistemului?
• Cum sunt transmise datele între procesoare?
• Care sunt primitivele de cooperare?
• Performanță și scalabilitate
• Cum se traduce totul în termeni de performanță?
• Cum scalează sistemul?
Arhitecturi
• Cu memorie partajată
• Cu memorie distribuită
• Hibride: cu memorie partajată și distribuită
Sisteme paralele cu memorie partajată
• Toate procesoarele accesează toată memoria.
Aceasta este disponibilă într-un spațiu de
adresare global.
• Procesoarele funcționează/execută
instrucțiuni independent unele de altele dar
partajează aceleași resurse de memorie.

• Modificările efectuate intr-o locație de memorie de către un procesor


sunt vizibile de către toate celelalte procesoare.
• În funcție de timpul de acces la memorie sistemele cu memorie
partajată se împart în două categorii: UMA și NUMA.
UMA [Uniform memory access]
• Sunt reprezentate de masinile SMP
(Symetric Multiprocessor Machines)
• Procesoarele sunt identice și au accelași
timp de acces la memorie și drepturi de
acces egale.
• Dacă un procesor face un update în
memorie atunci toate celelalte procesoare
sunt informate despre update.
• CC-UMA = Cache coherent UMA
• Coerența cache-ului este realizată la nivel
HW.
• NU au mai mult de cateva zeci de
procesoare.
NUMA [Non-Uniform Memory Access]
• Se obține de obiei prin conectarea fizică a doua sau mai multe SMP-
uri.
• Un SMP poate accesa direct memoria altui SMP.
• Nu toate procesoarele au timp egal de acces la toate memoriile.
• Timpul de acces prin legatura la alt SMP este mai mare.
• Dacă se menține coerența cache-ului atunci sistemul este de tip CC-
NUMA [Cache Coherent NUMA]
NUMA: Partajarea memoriei
• Avantaje
• Spațiul de memorie oferă o perspectivă ușpr de folosit pentru programator.
• Partajarea datelor între task-uri este rapidă și uniformă datorită apropierii memoriei
de procesoare.
• Dezavantaje
• Lipsa scalabilității între memorie și procesoare.
• Adăugarea de procesoare poate crește traficul pe o cale partajată intre memrie si un procesor.
Pentru sistemele care mențin coerența chache-ului vor apărea creșteri semnificative ale
traficului asociat activității de administrare a cache-ului/memoriei.
• Este responsabilitatea programatorului pentru asigurarea sincronizarii care să asigure
acces ”corect” la memoria globală. [nedeterminism]
• Cost: Proiectarea și realizarea sistemelor este din ce în ce mai dificilă pe măsură ce
crește numărul de procesoare.
Sisteme paralele cu memorie distribuită
• Memoriile distribuite necesită o rețea de
comunicare care să conecteze memoriile
procesoarelor.
• Procesoarele au memorie proprie,
locală. Adresele de memorie ale unui
procesor nu corespund cu cele ale altui
procesor, așadar nu există conceptul de
spațiu de memorie global accesibil tutror
procesoarelor
• Fiecare procesor operează/execută independent. Astfel, modificările efectuate
în memoria sa locală nu au nici un efect asupra celorlalte procesoare.
Connceptul de coerență a cache-ului nu există.
• Atunci când un procesor dorește să acceseze date ale altui procesor este de
obicei responsabilitatea programatorului să specifice explicit cum și când sunt
datele tranmsmise. Sincronizarea între task-uri este responsabilitatea
programatorului
SMP: Sisteme cu
memorie partajată
• Procesorul Intel® Core™ i7-970
este adeseori denumit SMP!

https://bit-tech.net/reviews/tech/cpus/intel-core-i7-970-review/1/
• 6 core-uri, 2-way multithread, 4-wide SIMD (pe 3 din cele 6
pipeline-uri)
• Coerență cache MESIF (https://en.wikipedia.org/wiki/MESIF_protocol )
• Ierarhia chache-ului face ca procesoare diferite să aibă costuri
diferite de acces la zone diferite de memorie => NUMA!
Sisteme paralele cu memorie distribuită
Distribuția memoriei
• Avantaje
• Memoria scalează cu numărul de procesoare. Creșterea numărului de
procesoare este direct proporțională cu creșterea dimenisunii memoriei.
• Fiecare procesor accesează rapid memoria proprie fără să interfereze și fără
costuri suplimentare necesare pentru meținerea coerenței cache-ului.
• Cost mic: putem folosi procesoare comune + rețea
• Dezavantaje
• Programatorul este responsabil de modul în care se realizează comunicația
între procesoare.
• Poate fi dificil de alocat structuri de date existente într-o memorie globala pe
o organizare distribuită.
• Timpi NUMA (Non-uniform Memory Access)
Comparație între arhitecturile cu memorie
partajată și distribută
Arhitectură CC-UMA CC-NUMA Distribuită
Exemple SMP Bull NovaScale Cray T3E
Sun Vexx SGI Origin Maspar
DEC/Compaq Sequent IBM SP2
SGI Challenge HP Exemplar IBM BlueGene
IBM POWER3 DEC/Compaq
IBM POWER4 (MCM)
Comunicație MPI MPI MPI
Threads Threads
OpenMP OpenMP
shmem Shmem
Scalabilitate x10 de procesoare X100 de procesoare X1000 de procesoare
Limitări Lățimea de bandă Lățimea de bandă Admimistraea și
Memorie-CPU Memorie-CPU șu NUMA programarea sunt dificil de
dezvoltat și întreținut
Disponibilitate SW Mii de ISVs Mii de ISVs Sute de ISVs
IBM BlueGene
(https://en.wikipedia.org/wiki/IBM_Blue_Gene )
• 3 generații care au condus în
https://www.top500.org/ :
• BG/L: Feb 1999
• BG/P: June 2007
• BG/Q: Nov 2011
• 2015 s-a oprit dezvoltarea
• 15 instalări
• Aplicații
• Jocul de șah
• Simularea a 1% din cortex (1.6 bn de neuroni cu
approx. 9 tn de conexiuni)
Arhitecturi hibride de memorie
• Cele mai mari și mai rapide sisteme din
lume folosesc atât memoria partajată cât
si cea distribuită.
• Componentele cu memorie partajată sunt
mașini CC-SMP (cache coherent symetric
multiprocessor); procesoarele accesează
memoria componentei în mod global.
• Memoria distribuită este obținută prin conectarea în rețea a mai multor SMP-
uri. Deoarece fiecare SMP are acces direct numai la memoria sa (NU la
memoria altui SMP), este nevoie de comunicație de date pentru a transfera
date de la un SMP la altul.
• Trend-ul actual arată că aceste sisteme sunt cele vor domina domeniul HPC.
• Avantajele si dezavantajele: aspectele comune pentru arhitecturile cu memorie
parajată și distribuită.
Multicalculatoare (Message-Passing MIMDs)
• Procesoarele sunt conectate în rețea
• Poate fi conexiune Etherner sau o magistrală.
• Fiecare procesor are memorie locală si poate accesa doar memoria
locală.
• Datele sunt transmise între procesoare prin mesaje, asa cum este
specificat în aplicație printr-un limbaj adecvat (e.g., MPI, PVM).
• Problema este împărțită in task-uri care se execută concurent pe
procesoare.
• Unui procesor îi pot fi alocate mai multe task-uri.
Dezavantaje ale multicalculatoarelor
Sunt derivate din dezavantajele transmiterii de mesaje:
• Programatorul trebuie să apeleze explicit codul care transmite
mesajul.
• Programarea de nivel jos (i.e., explicită) predispune la erori
• Datele nu sunt partajate ci sunt copiate, ceea ce duce la creșterea
dimeniunii totale a datelor administrate.
• Integritatea datelor: este dificil de menținut corectitudinea datelor
atunci când administrăm mai multe copii ale aceleiași unități de
memorie
Avantaje ale multicalculatoarelor
• Ușurința accesului la date
• Permite ca sisteme diferite să proceseze aceleași date în mod
independent
• Permite updrade ușor al sistemelor atunci când avem la dispoziție
procesoare mai rapide.
• Arhitecturile hibride (cluster de SMP-uri) sunt o soluție la îndemână
• Asigură în același timp partajarea și distribuția.
Rolul arhitecturii de calcul paralel/distribuit
• Proiectarea și implementarea unei arhitecturii de calcul
paralel/distribuit este să maximizeze performanța și programabilitatea
(i.e., usurința de dezvoltare/testare/depanare/intreținere) sistemului
în limitele impuse de tehnologie și cost.
• Paralelismul arhitectural
• Se poate aplica la toate nivelel unui sistem
• Oferă o persepctivă deosebită asupra arhitecturii unui sistem
• Tradițional, elementele sistemului (procesoare și memorii) sunt colocalizate
• Creșterea lărgimii de bandă permite paralelism la distanțe mai mari.
Tendințe tehnologice
• Tehnologiile actuale continuă să crească densitatea chip-urilor
• Frecvența de lucru pentru arhitecturile single-core necesită prea
multă energie
• Unitilizarea tehnologiilor/sistemelor obișnuite (en., comodity) sau
gata de utilizat (en., off-the-shelf) necesită costuri scăzute.
• Microprocesoarele multi-core (i.e., AMD, IBM, Intel) sunt comune.
• Lățimea de bandă mare (i.e., 10GB Ethernet) este în general
disponibilă
Tendințe arhitecturale
• Împachetarea soluțiilor paralele
în *șasiuri* comune IBM BladeCenter S Chassis
• Servere Blade (i.e., IBM, Dell, HP, etc.)
• SW
• Windows Compute Cluster Server 2003 apărut în 2006 este prima tehnologie
HPC oferită de Microsoft.
• In prezent avem Windows Server 2019 care permite sistemelor server să luceze
împreună ca un cluster.
D.p.d.v. economic
• Reducerea costurilor serverelor cu bandă largă de interconectare
determină din ce în ce mai multe aplicații să devină paralelizabile.
• Micropucesoarece comune sunt rapide și ieftine
• Dezvoltarea lor costă zeci de milioane de USD
• DAR, se vand în cantități foarte mari, mult mai mari decât supercalculatoarele.
• Standardizarea face ca SMP-urile să devină un lucru comun
• Sistemele desktop
• Mai multe procesoare mai mici, sau unul mai mare?
• Arhitectură multiprocesor pe un singur chip.
Exemplu: Blade server
• 14 Blades intr-un 9U Chassis Form Factor
• IBM BladeCenter HS22 7870 2x QC Xeon X5560
2.8GHz 24GB costa approx. 200 euro.
• Rețea de înaltă performanță
• 4 surse de 2900W
• Descriere IBM BladeCenter H Chassis
• https://lenovopress.com/tips0996-bladecenter-h
Prezentare generală BladeCenter
CPU
Blade
CPU
Blade
CPU
Blade
CPU
Blade
CPU
Blade
Switch
CPU
Module
Blade
CPU
Blade
CPU
Switch
Blade Module
CPU
Blade
.
.
.

Mgmt
Module
Mgmt
Module
Prezentare generală BladeCenter H
I/O Bridge

HS Switch 1
• Switch de mare viteză
Blade 1
• Ethernet
HS Switch 2
• 4x (16 wire) blade links
Blade 2
4x (16 wire) bridge links
I/O Bridge

I/O Bridge 3/ SM3
• 1x (4 wire) Mgmt links
.. HS Switch 3
• Uplinks: Up to 12x links for
. HS Switch 4 IB and at least four 10Gb
I/O Bridge 4 / SM4
links for Ethernet
Switch Module 1
• I/O Bridge
Switch Module 2 • Ethernet, Fible Channel
Blade 14
• Dual 4x (16 wire) cablare
internă către fiecare HSSM
Mgmt Mod 1

Mgmt Mod 2
Arhitectura sistemelor distribuite
• Arhitecturi de tip client-server
• Arhitecturi cu obiecte distribuite
• CORBA: ORB (Object request brokers)
• Arhitecturi peer-to-peer și service-oriented

Distribuția = Procesarea are loc pe mai multe sisteme, în nici un caz pe


o singură mașină
Ingineria aplicațiilor distribuite este pentru enterprise computing
systems.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile client-server
• Serviciile sunt distribuite și sunt denumite client.
• Serverele care oferă servicii au tratament diferit față de clienții care utilizează
serviciile.
• Arhitecturile cu obiecte distribuite
• NU există nici o distincție între clienți sp servere
• Orice obiect din sistem poate oferi sau folosi servicii ale altor obiecte.
• Middleware
• Software care administrează și susține diferite componente ale sistemului distribuit
(i.e., se află în mijlocul sistemului).
• Este de obicei SW de tip off-the-shelf (i.e., CORBA, Java RMI, SOAP, EJB), adică nu
este special scris.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile multiprocesor
• Cele mai simple sisteme distribuite
• Distribuția proceselor pe procesoare este sub controlul unui dispecer (en.,
dispatcher).
• Arhitecturile client-server
• Aplicația = mulțime de servicii oferite de servere + mulțime de clienți care
folosesc/apelează serviciile
• Clienții știu/cunosc serverele, dar derverele nu trebuie să aibă cunoștință de
clienți.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile client-server (cont)
• Clienții și serverele sunt procese logice (pot avea SO diferite!).
• Diferențele de nivel jos (SO, limbaj de programare) sunt irelevante dacă serverele și
clienții folosesc aceleași protocoale de comunicație și au aceleași aplicații.
• Alocarea proceselor la procesoare nu este neapărat 1:1.
• SW de comunicație este cel care permite clienților să interacționeze cu
serverele: TCP/IP
• Funcționalitățile aplicației pot fi împărțite între cleint și server pentru a
optimiza utilizarea resurselor.
• Clase de aplicații: Host-based, Server based, Client-based, Cooperativ.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile client-server (cont)
• Clase de aplicații
• Host-based: Toată procesarea este efectuată pe server. Clientul este doar un terminal.
• Server-based: Serverul efectueaza procesarea, dar clientul are GUI pentru interacțiune.
• Cooperative: Atât serverul cât și clentul rulează logică din aplicație.
• Complex de setat si întreținut, dar oferă productivitate și eficiență mare.
• Client-based: Toată procesarea (View+Controller+Model logic) se efectuază la client.
• Serverul realizează doar validarea datelor și funcțiile pe baza de date
• Este cea mai utilizată athitectură pentru desktop applications
• Nivelele devin: Client, server de aplicații (middle-tier server), Back-end server.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile client-server (cont)
• Sunt de obicei pe 3 nivele
• View/prezentare – client [thin/fat]
• Controller/application processing – server
• Model/administrarea datelor – server
• Exemplu: Homebank = Client [mobile/web] – Web Server – Database server
• Tipuri de servere
• Servere de baze de date
• Servere de aplicații
• Servere de resurse
• Servere de calcul (en., computation)
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile client-server (cont)
• Avantaje:
• Acces facil la baze de date
• Partajarea resurselor HW si SW
• Scalabilitatea
• Rapiditatea în dezvoltare
• Limitări
• Costuri de implementare: echipamente, integrare, timp mare
• Tehnologii complexe
• Unelte de administrare necustomizabile
• Securiate
• Roluri multiple: admin, owner, proiectant, utilizator, etc.
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile cu obiecte distribuite
Arhitectura sistemelor distribuite (cont.)
• Arhitecturile
cu obiecte
distribuite
(cont.)
Arhirectura SD: Middleware d.p.d.v. logic
Fialbiltatea sistemelor distribuite
• Transmiterea de mesaje fiabilă garantează livrarea datelor (mesaje,
obiecte, fișiere, etc.)
• Uneori nu este nevoie șă confirmăm clentului că cererea/datele/etc. au ajuns la
server, dar este util.
• Opțiunea 1. Dacă transmirerea eșuează atunci procesul care a trimis
cererea este notificat.
• Opțiunea 2. Mesajele/datele sunt trimise fără a primi înapoi informații
despre succes sau eșec (i.e., TCP vs. UDP)
• Aceasă opțiune reduce complexitatea și costurile suplomentare de comunicație.
• Aplicațiile care necesită confirmarea ca mesajul a fost transmis,
confirmarea însăși va fi un mesaj.
Fialbiltatea sistemelor distribuite (cont.)
Cu blocare vs. Fără blocare
• Fără blocare
• După ce un proces a trimis un Send/Receive nu se blochează
• Mod eficient și flexibil de a utiliza transmiterea mesajelor de către procese
• Testarea și depanarea este dificilă deoarece este dificl de reprodus contextul (i.e.,
secvența/ordinea exactă de desfășutrare în timp a operațiilor)
• Cu blocare
• Reprezintă varianta sincronă de comunicare
• Procesul care trimite cererea nu cedează controlul până când mesajul nu a fost
transmis (i.e., trimis si primtă confirmarea de recepționare)
• Procesul care primește mesajul nu cedează controlul (i.e., nu continuă) până când
acesta nu deține efectiv mesajul sau mesajul se află într-un buffer.
Apelurile la distanță (en., remote procedure
call)
• Permit programelor de pe sisteme diferite să interacționeze prin
apelări simple.
• Sunt folosite pentru a accesa servicii aflate la distanță
• Larg acceptate și des folosite pentru a încapsula comunicația în
sistemele distribuite.
• Codul de comunicare al unei aplicații se generează automat.
• Modulele client și server pot fi mutate de pe o platformă (i.e., mașină
+ SO) pe alta cu modificări minime.
Paralelismul in Java [bazat pe Task-uri]
• Este asemănător cu cel din C++.
• Și multithreading-ul este asemănător cu cel din C/C++.
• Aceeași abordare:
• Crarea unui Task
• Treminarea unui Task
• Realizarea grafului de calcul (en., computation graph) folosind Fork/Join
• Task-urile rulate în paralel sunt independente (i.e., ordinea de
execuție nu contează).
• Task-urile sunt definite în funcție de logica pe care o execută
(paralelism funcțional) sau de datele utilizate în cadrul procesării
(paralelism al domeniului).
Paralelismul in Java [bazat pe Task-uri] (cont.)
• Java SE pune la dispoziție Fork/Join Framework pentru a putea
implementa paralelismul cu ușurință în aplicații.
• Dificultăți
• Utilizarea colecțiilor care nu sunt thread-safe, ceea ce înseamnă că mai multe
fire nu pot accesa o colecție fără a produce interferențe sau erori de
consistență a memoriei.
• Obs: Paralelismul nu este automat mai rapid decât varianta secvențială, dar
poat efi dacă aveți suficiente date și procesoare.
• Fluxurile (en., stream) în Java put rula serial sau paralel. În situația rulării
paralele, Java RTE (runtime) prin JVM partiționează fluxul în mai multe sub-
fluxuri.
Execuțiea paralelă Vs. Execuția paralelă concurentă
Paralelism
• Se referă la o aplicație
care împarte task-urile
în sub-task-uri mai
mici care se execută în
paralel pe mai multe
procesoare în același
timp.
• NU se referă la
execuția paralelă
concurentă.
Paralelismul task-urilor
• Task-uri diferite pot folosi sau nu aceleași date
• De aici o accelerare mai mică
• Procesarea este asincronă
• Gradul de paralelizare este proporțional cu numărul de task-uri
independente obținut/creat.
• Echilibrarea încărcării depinde de diponibilitatea HW li de algoritmii
de planificare (statici sau dinamici)
Fork/Join Framework în Java
• Pachetul java.util.concurrent include clase și interfețe care susțin programarea
paralelă.
• Simplifică procesul de creare a firelor, utilizare a lor, automatizează mecanismul de alocare a
proceselor pe procesoare.
• Clase de bază
ForkJoinTask<V>: este o clasă abstractă care definește un task.
- Metoda fork():
- crează un task (în mod asemenător cu cu un fir creat de clasa Thread).
- Permite execuția asincronă a task-ului.
- Metoda join():
- Permite așteptarea până când task-ul apelant se termină.
- Metoda invoke():
- Combină fork și join într-un singur apel.
Fork/Join Framework în Java (cont.)
• ForkJoinPool: este o clasă care permite administrarea și monitorizarea
execuției de taskuri ForkJoinTask.
• RecursiveAction: este o derivare abstractă a clasei ForkJoinTask. De
obicei, derivăm această clasă pentru a crea un task care NU
returnează un rezultat sau returnează void. Metoda compute()
definită în această clasă este suprascrisă pentru a conține
procesarea/codul task-ului.
• RecursiveTask<V>: este o derivare abstractă a clasei ForkJoinTask. De
obicei, derivăm această clasă pentru a crea un task care returnează un
rezultat. Metoda compute() definită în această clasă este suprascrisă
pentru a conține procesarea/codul task-ului.
Strategia Fork/Join Framework
• Framework-ul folosește strategia divide and conquer recursivă pentru a
implementa procesarea paralelă.
• Împarte un task în subtask-uri mai mici; apoi, fiecare subtask în alte subtast-uri mai
mici, etc.
• Împărțirea este aplicată recursiv până când fiecare subtask este suficient de mic
pentru a fi executat secvențial.
• Exemplu: Incrementarea valorilor dintr-un tablou de N numere.
• Strategia divide and conquer poate fi aplicată până când obținem subtask-uri de
dimensiune 1 (i.e., un element din tablou). Apoi, subproblemele de dimensiune 1
sunt executate în paralel de procesoarele disponibile.
• Pe un sistem secvențial (cu un procesor) ar trebui să parcurgem întreg tabloul pentru
efectuarea procesării (i.e., incrementării), ceea ce at fi ineficient d.p,d,v, al procesării
paralele.
Strategia Fork/Join Framework (cont.)
Operația de invocare/apleare în paralel se realizează prin:
• fork: crează două sau mai multe task-uri.
• join: întrerupe/suspendă firul curent (i.e., apelant) până când task-ul
nou se finalizează.
Programarea concurentă în Java
• Cuvântul cheie synchronized în Java asigură:
• că doar un singur fir poate executa un bloc de cod în același timp
• că fiecare fir care intră într-un bloc de cod sincronizat vede efectele tuturor
modificărilor anterioare (i.e., ultima variantă) care erau păzite de aceeași
blocare
• Sincronizarea este necesară pentru accesul reciproc exclusiv la blocuri
și pentru o comunicare fiabilă între fire.
• Utilizarea cuvântul cheie synchronized pentru definirea unei metode
• asigură că un singur fir poate intra în această metodă la un moment dat. Un
alt fir care apelează această metodă trebuie să aștepte până când primul fir
părăsește metoda.
Programarea concurentă în Java
Clase de bază
• java.lang.Thread: implementează interfața java.lang.Runnable care
definește metoda run().
• Metoda run() este apelată de obiectul de tip Thread care conține codul care
trebuie executat.
• Runnable reprezintă sarcina care trebuie rulată iar Thread este task-ul care
rulează sarcina.
• java.util.concurrent.ExecutorService: interfață care este un înlocuitor
de nivel superior pentru lucrul direct cu fire.
• Pot rula sarcini asincrone și pot admimistra un grup/pool de fire care nu
trebuie create manual.
Programarea concurentă în Java (cont.)
• java.util.concurrent.Executor: interfață care permite obiectelor care o
implementează sa execute sarcini Runnable.
• Se oferă astfel o modalitatea de a decupla/separa trimiterea task-ului de
mecanica executării task-ului (i.e., utilizarea firului, planificarea, etc.)
• Exemplu:
În loc de apelul Thread(new(RunnableTask())).start() necesar fiecărui task
Putem avea secvența: cu un executor care rulează task-ul imediat
Executor executor = anExecutor; class DirectExecutor implements
executor.execute(new RunnableTask1()); Executor {
executor.execute(new RunnableTask2()); public void execute(Runnable r) {
... r.run();
}
}
Programarea concurentă în Java (cont.)
• Totuși, de obicei task-urile sunt executate de un fir separat, nu de firul
apelant. Acest executor crează cate un fir nou pentru fiecare task.
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
Fluxuri Java în paralel
• Un flux (i.e., stream) poate fi executat serial sau paralel.
• Atunci cand dorim executie paralela Java RTE (runtime) partiționează
fluxul in mai multe sub-fluxuri.
• Astfel, subfluxurile se execută în paralel iar rezultatele sunt agregate.
default Stream<E> parallelStream() S parallel()
Returns a possibly parallel Stream Returns an equivalent stream that is parallel. May
with this collection as its source. return itself, either because the stream was
already parallel, or because the underlying stream
state was modified to be parallel.
This is an intermediate operation.
Algoritmi paraleli si
distribuiți
Proiectarea programelor paralele /Tipuri de execuție paralelă
+OpenMP
Modele de programare paralelă
• Elementul cheie este sarcina (en., task).
• Un calcul paralel constă din una sau mai multe sarcini care se executa
concurent.
• Un task este reprezentat de un program secvențial și memoria locală. De
asemenea, acesta trebuie să aibă definită o intrefață (în sensul de API) de
comunicare cu mediul. Înterfața este definită prin inports si outports,
acestea specifică modul în care sunt transmise datele de intrare și cum se
obțin rezultatele/raspunsurile.
• Un task poate exacuta următoarele 4 acțiuni în plus față de citire/scriere
din/în memoria locală: trimiterea de mesaje la outport, recepționarea de
mesaje prin inports, crearea unui nou task și terminarea taskului.
Modele de programare paralelă (cont.)
• Operația de trimitere este asincronă, aceasta se execută și se
finalizează imediat.
• Operația de recepționare este sincronă, aceasta determină blocarea
task-ului până când un mesaj este recepționat.
• Perechile outport/inport se pot conecta prin cozi de mesaje,
denumite canale. Canalele pot fi create sau șterse.
• Task-urile pot fi asignate procesoarelor fizice; modul în care este
asignat un task nu afectează semantica acestuia. Da, mai multe
task-uri se pot asigna unui singur procesor.
Task-uri
• Utilizarea în mod abstract a task-urilor ne oferă un mecanism de
înglobare a principiului localizării datelor:
• Datele conținute în memoria locală a unui task sunt ”apropiate”; toate
celelalte date sunt “îndepărtate”.
• Utilizarea canalelor oferă un mecanism pentru a specifica faptul că ”un task
are nevoie de date/rezultat din alt task pentru a putea continua”. Acest
aspect poartă numele de dependința datelor (en., data dependency).
• Întrebări:
• Ce este un computation graph?
• Ce algoritmi clasici din teoria grafurilor pot fi utili si în ce context?
• Cum se se pot paraleliza astfel de algoritmi?
Task-uri: exemplu
• Dorim construirea unui pod din traverse.
• Traversele sunt construite/încărcate la FABRICĂ și sunt asamblate pe
ȘANTIER.
• Traversele sunt transportate cu camioanele de la fabrică la șantier.

FABRICĂ ȘANTIER

• Fabrica si santierul pot reprezenta task-uri diferite.


• Limitare: Fapbrica poate produce traverse mai repede decât acestea pot fi
montate pe șantier.
Task-uri: exemplu (cont.)
• Pentru a împiedica ȘANTIERUL să se umple de traverse trebuie ca inginerii
să ceară explicit traverse atunci când rezervele scad.
• Această abordare îmbunătățită necesită un al doilea canal de comunicație
care să înformeze fabrica atunci când trebuie trimise traverse. Acest canal
va anunta fabrica să oprească trimiterea de traverse atunci cand podul este
finalizat.

FABRICĂ Trimite! Trimite! ȘANTIER

• Abordare practică: mai multe drumuri, mai multe mașini, mai multe puncte
de lucru pe șantier, etc.
Descompunerea și alocarea: Metodologia Foster
• Concepte și terminologie
• Comunicare = schimbul de date între task-uri care se execută în paralel
• Necesită timp
• Sincronizare = coordonarea în timp real a task-urilor paralele
• Conduce la așteptări și întârzieri
• Granularitate
• Mare => comunicare puțină, calcule/funcții mari
• Mică => comunicare multă, calcule/funcții mici
• Costurile suplimentare apar datorită
• Sincronizărilor și schimburilor de date
• Compilatorului, bibliotecilor, uneltelor, SO, etc.
Descompunerea
• Este primul pas în proiectarea/dezvoltarea aplicațiilor paralele
• Constă în identificarea task-urilor care pot fi executate concurent
• Inițial, numărul de task-uri trebuie să fie cât mai mare (granularitate fină)
• Granularitatea fină oferă cea mai mare flexibilitate în ceea ce privește proiectarea
unui algoritm paralel.
• Ulterior evaluarea cerințelor de comunicare, arhitectura sistemului pe care va rula
aplicația sau aspectelele legate de proiectarea software vor definiexact modul de
paralelizare
• Task-urile pot fi identice, diferite sau de dimensiune necunoscută la rulare
• Descompunerea se poate reprezenta printr-un graf de dependințe și
interacțiuni ale task-urilor.
Descompunerea (cont.)
• O descompunere de bună calitate împarte atât logica de calcul
necesară, cât și datele care trebuie prelucrate.
• Primul pas se referă intotdeauna la descompunerea datelor.
• Apoi, trebuie determinat modul în care sunt efectuate procesările pe datele
descompuse
• Obs: Acest mod de descompunere este denumit descompunere de domeniu.
• Descompunerea funcțională are în vedere mai întâi descompunerea
procesării care trebuie efectuată, apoi are loc descompunerea
datelor.
• Obs. Aceste două modalități de descompunere sunt complementare,
dar pot fi aplicate la module diferile ale aceleiași probleme, sau pot fi
aplicate la aceeași problemă pentru a obține algoritmi alternativi.
Descompunerea (cont.)
• Scopul principal al descompunerii este să evite replicarea procesărilor
sau a datelor.
• Descompunerea fină (en. fine-grained) oferă cea mai mare flexibilitate în ceea
ce provește potențialul de paralelizare a algoritmului serial.
• Descompunerea domeniului (a datelor)
• Ideal ar fi să putem împărți datele în părți egale
• Apoi, împărțim calculele care trebuie efectuate prin alocarea fiecărei părți de
calcul a datelor necesare pe care lucrează. Astfel se obțin task-uri
• Task = datele si operațiile care trebuie efectuate pe acele date.
• O operație poate necesita rezultate/date de la mai multe task-uri => nevoia de
comunicare
Descompunerea domeniului

Descompunerea domeniului pentru o problemă cu date 3D. Datele care pot fi


asociate cu un task sunt marcate cu gri. Descompunerea 3D este cea mai fină,
oferă cea mai mare flexibilitate și este adoptată în stadiile inițiale ale
proiectării
Descompunerea funcțională
• Reprezintă un mod alternativ de abordare a problemelor.
• Atenția maximă este acordată procesărilor care trebuie efectuate, NU
datelor.
• Procesarea necesară trebuie împărțită în părți disjuncte; dacă datele necesare
sunt de asemenea disjuncte atunci putem defi task-uri și descompunerea este
finalizată.
• Suprapunerile semnificative conduc la nevoia de comunicație în scopul
evitării replicării datelor.
• Trebuie avută în vedere în ceea ce privește investigarea diverselor modalități
de paralelizare.
Descompunerea funcțională (cont.)
• Modelul de calcul al climei =
• Modelul atmosferic determină
viteza vântului, care este folosită
de modelul oceanului
• Modelul oceanului detrmină
temperatura apei la suprafață, care
este folosită de modelul
atmosferic,
• Etc.
Descompunerea funcțională (cont.)
• Ajută la modularizarea/structurarea programului/aplicației
• Modele complexe sunt de cele mai multe ori structurate/împărțite în
colecții de modele simple care sunt conectate prin interfețe.
• Exemplu: Modelul climatic = colecție de modele (atmosfră, ocean,
hidrologie, gheață, surse de dioxid de carbon, etc.)
Pașii necesari partiționării/descompunerii
• Partiționarea poate produce mai multe moduri de descompunere a
problemei
• Pașii de verificare a corectitudinii sunt:
1. Cat de multe sunt task-urile în raport cu procesoarele pe care le
avem disponibile pe masina care va executa programul?
• Trebuie sa avem cel puțin un ordin de marime mai multe task-uri decat
procesoare, altfel nu avem flexibilitatea necesară.
2. Descompunerea propusă evită procesările/nevoile de memorie
redundante?
• Dacă răspunsul este NU atunci propunerea nu va fi scalabilă pentru input
mare.
Pașii necesari partiționării/descompunerii
(cont.)
3. Sunt task-urile de dimensiune comparabilă?
• Dacă nu, adunci va fi dificil să alocăm fiecărui procesor o cantitate egală de calcule,
așadar vom avea o problemă legată de balansarea încărcării.
4. Numărul de task-uri se scalează cu dimensiunea problemei?
• Ideal ar fi ca o creștere a dimensiunii problemei să determine o creștere a numărului de
task-uri, si nu creșterea dimensiunii task-urilor existente. În această situație, algoritmul
nu va rezolva probleme mai mari atunci când vom avea la dispoziție mai multe
procesoare.
5. Ați identificat mai multe variante/opțiuni de descompunere?
• Maximizarea flexibilității în fazele avansate ale proiectări se poate asigura prin
identificarea mai multor variante/opțiuni de descompunere, atât a domeniulu cât și
funcțională.
Comunicarea
• Task-urile obținute prin descompunere sunt gândite să se execute
concurenty, dar de multe ori ele nu se pot executa independent.
• Comunicarea = fluxul de informații dintre task-uri.
• Poate fi specificată în 2 pași:
• Definim un canal între task-un care cere date (ex., consumator) și task-ul care
deține/calculeză datele necesare (ex., producător).
• Specificăm mesajele care vor fi trimise si primite prin canalele definite.
• Obs. În funcție de tehnologie/implementare codarea algoritmului poate să nu necesite
crearea explicită a canalelor. Totuși, înțelegerea acestor aspecte permite evaluarea
calitativa și cantitativă a problemelor legate de costurile de comunicare și de localizarea
datelor.
Comunicarea: tipuri
• Locală/globală:
• Locală: task-ul comunică cu o mulțime mică de alte task-uri ”vecine”
• Globală: fiecare task comunică cu multe alte task-uri
• Structutată/nestructurată
• Structurată: se folosește o structură regulată, ex. Arbore sau grid.
• Nestructurată: rețeaua poate fi un graf oarecare.
• Static/dinamic
• Static: identitatea task-urilor cu care se comunică NU se schimbă în timpul rulării
• Dinamic: task-urile cu care se comunică sunt determnate la rulare și se pot schimba
în timp.
• Sincron/asincron
• Sincron: producătorii și consumatorii se execută coordonat
• Asincron: consumatorul obține date fără a coopera cu producătorul
Comunicare locală: exemplu
X [i,j] (t+1) = (4* X [i,j] (t) + X [i-1,j] (t) + X [i+1,j] (t) + X [i,j-1] (t) + X [i,j+1] (t) )/8
Task-urile si structura de canale pentru
calculul diferenței finite într-un tabel 2D cu
un șablon de updatare în 5 puncte (folosit ca
metodă numerică simplă pentru metoda
Jacobi de calcul a diferențelor finite).
- Descompunerea domeniului:
Un task alocat punctului X[i,j] trebuie să
determine secvența:
X [i,j] (1) , X [i,j] (2) , X [i,j] (3) , ...
... Dar fiecrare calcul are nevoie de 4
secvențe corespunzătoare punctelor vecine.
Comunicare locală: exemplu (cont)
• Fiecare task va trebui să execute următorul algoritm
for t=0 to T-1
send X [i,j] (t) către fiecare vecin
primește X [i-1,j] (t) , X [i+1,j] (t) , X [i,j-1] (t) , X [i,j+1] (t) de la vecini
determină X [i,j] (t+1) conform cu ecuația
• Toate punctele pot fi updatate concurent!
Comunicarea globală
• Avem multe task-uri care participă.
• Nu este suficient să identificăm perechile producător/consumator.
• O astfel de abordare ar implica prea multe canale de comunicare sau ar
putea restricționa oportunitățile de execuție concurentă.
• Problema sumei centralizate
S = sumi=0, N-1 Xi
• Presupunem ca un task cere efectuarea acestui calcul, prin cererea valorilor
X0, X1, ... de la task-urile 0, 1, ... Asadar am putea proiecta o structură de
comunucare care să permită comunicarea fiecărui task cu task-ul S (cel care
doreste să efectueze suma) în mod independent. Totusi, deoarece S poate
primi și aduna o singură valoare la un moment dat, înseamnă că avem
nevoie de O(N) timp, ceea ce nu reprezintă o paralelizare bună!
Comunicarea globală (cont.)
• Aspecte care opresc/împiedică paralelismul
• Algoritmul este centralizat: el nu distribuie nici calculele și nici comunicarea.
Un singur task (i.e., managerul S) trebuie să participe la fiecare
operație/calcul.
• Algoritmul este secvențial: Nu permite ca efectuarea concurentă de calcule
sau comunicare.
• Soluție:
• Distribuirea comunicării și calculelor!
Distribuirea comunicării și calculelor
• Distribuim calcularea sumei celor N numere alocând fiecărui task i
sarcina de a detrmina suma S[i] = X[i] + S [i-1]

• Task-ul N-1 trimite valoarea calculată vecinului său


• Se distribuie N-1 adunări și transmiteri, dar execuția concurentă este
posibilă numai dacă mai multe adunări se execută concurent.
• Determinarea sumei costă încă N-1 pași.
• Soluția: Divide and Conquer
Utilizarea divide and conquer pentru
paralelizare
• Împărțim problema în două sau mai multe probleme mai simple de
dimensiune aproximativ egală (ex., adunpm N/2 numere).
• Acest proces este aplicat recursiv până când problema nu mai poate fi
împărțită (i.e., avem de adunat două numere).
• Strategia este eficientă d.p.d.v. Al paralelizării atunci când
subproblemele pot fi rezolvate concurent.
• Pentru N = 2n , n > 0 avem:
Sumai=0, 2n-1 = Sumai=0, 2n-1-1 + Sumai=2n-1, 2n-1
• Cele două sume pot fi executate concurent.
Utilizarea divide and conquer pentru
paralelizare (cont)
• Sumele care se găsesc pe
același nivel pot fi executate
concurent.
• Arborele are înlțime
h = log N
• Detrminarea sumei se
realizează în log N pași în loc
de N.
• Structura de comunicare este
• regulată, fiecare task comunică
cu un număr mic de vecini.
• Sincronă: task-urile producător
trimit explicit rezultatele către
consumator.
Comunicarea asincronă
• Avem task-uri *de date (D)* separate
pentru efectuarea cererilor de citire și
scriere pe o structură de date
distribuită.
• Task-urile *de calcul (C)* trimit cereri de citire/scriere către cele 8
celule/elemente de date distribuite la cele 4 task-uri D.
• Liniile pline reprezintă cereri; liniile întrerupte reprezintă răspunsuri.
• Un task C și un task D pot fi plasate pe fieceare dintre cele 4 procesoare
pentru a distribui datele și procesările echitabil.
Comunicarea asincronă (cont.)
• Scenariul prezentat anterior apare atunci când procesarea este
structurată ca o mulțime de task-uri care priodic efectuază operații de
citire/scriere din/în elementele unei structuri de date partajată.
• Presupunem ca această strucură de date este prea mare sau este prea
frecvent accesată pentru a putea fi încapsulată într-un singur task.
• Așadar, este nevoie de un mecanism de distribuire a structurii de date care să
perimită operații asincrone de citire sau scriere asupra elementelor ei.
• Mecanisme posibile
• Structura de date este distribuită între task-uri C (de calcul). Fiecare task
efectuază atât procesări cât și cereri de date localizate la alete task-uri.
Task-ul trebuie sa-și întrerupă periodic execuția proprie și să răspună cererilor
care sunt în așteptare.
Comunicarea asincronă (cont.)
• Mecanisme posibile (cont.)
• Structura de date distribuită este încapsulată într-o mulțime separată de task-uri
care raspund numai la cererile de citire sau scriere.
• Pe sistemele acre permit modelul de programare cu memorie partajată, task-urile C
(de calcul) pot accesa datele partajate fără a fi nevoie de alte mecanisme. Totuși,
trebuie verificată corectitudinea ordinii în care se efectuază operațiile deoarece
putem ajunge în situație de nedeterminism.
• Caracteristicile de performanță variază de la un sistem la altul.
• Limitări
• Prima strategie poate conduce la programe nemodulare. Pooling-ul (răspunsul la
cereri în așteptare) este o operație costisitoare pe unele sisteme.
• Cea de-a doua strategie nu nu exploatează localizarea datelor. Practic, nu există
date locale.
Verificarea modului de comunicare
Evaluarea modului în care a fost proiectată comunicarea
1) Toate task-urile definite realizează aproximativ același număr de oprații de
comunicare?
- Operațiile de comunicare trebuie distribute cat mai uniform.
2) Fiecare task comunică cu un număr mic de vecini?
- Daca toate task-urile comunică cu multe alte task-uri trebuie luată în calcul abordarea cu
comunicare locală în locul comunicării globale.
3) Operațiile de comunicare se desfășoară în paralel?
Dacă nu, atunci algoritmul nu este scalabil. Obținerea concurenței poate fi făcută prin divide
and conquer.
3) Procesările/calculele asociate task-urilor se desfășoară concurent ?
Dacă nu, algoritmul nu este scalabil și trebuie avută în vedere reordonarea operațiilr de
comunicare și procesare.
Ghid de paralelizare (Foster)
• Identifică modulele cu necesități mari de calcul (en., computational
hotspots)
• Determină ceea ce merită să fie paralelizat
• Partiționează/Împarte problema în task-uri mici și cât mai independente
• Determină/crează/definește paralelismul
• Identifică necesitățile de comunicare dintre task-uri
• Stabilește constrângerile de comunicare dintre task-uri
• Aglomerează task-urile mai mici în task-uri mai mari
• Grupează task-urile de bază impreună astfel încât comunicarea să devină minimă.
Mare atenție la balansarea încărcării.
• Alocă task-urile și datele pe procesoare
• Balansează încărcarea procesoarelor încercând sa minimizezi comunicarea
De la descompunere la alocare
• Metodologia lui Foster exemplifică procesul de dezvoltare software
care este strâns legat de proiectarea paralelă:
• Descompunem problema în task-uri care au potențial să fie executate
în paralel
• Descompunerea poate fi reprezentată printr-un graf de dependințe și
interacțiuni între task-uri
• O problemă poate fi descompusă în multe moduri în task-uri; acestea pot fi
de aceeași mărime sau de marimi diferite.
• Analizăm nevoile de comunicare între task-uri
• Grupăm task-urile care comunică intens
• Asignăm task-urile pe procesoare
Criteriile de alocare
• Trebuie maximizată concurența prin alocarea task-urilor
independente la procesoare diferite
• Timpul total de execuție trebuie minimizat prin prioritizarea
task-urilor care se află pe calea critică a grafului de dependințe.
• Minimizarea interacțiunii trebuie relizată prin alocarea task-urilor cu
grad mare de interacțiune mutuală la același proces.
Proiectarea
algoritmilor
paraleli
Proiectarea algoritmilor paraleli
• Scop: Pentru o anumită problemă determinați un algoritm care
îndeplinește condițiile de concurență, scalabilitate și localizare.
• Concurență: avem prelucrări paralele
• Scalabilitate: Algortmul scalează bine (liniar) pentru date de intrare mai mari,
sistem cu mai multe procesoare, memorie mai multa, etc.
• Localizare: alocarea task-urilor si datelor este eficientă.
• Cele mai multe probleme
• Au mai multe soluții algoritmice seriale; totuși, presupunem ca avem la
dispoziție soluția optimă.
• Au și mai multe soluții paralele. Ingredientele unei soluții paralele sunt:
gradul de descompunere, graful de dependințe al task-urilor, modul de
alocare al task-urilor, implementarea.
Aglomerarea
• Are ca scop definirea unui număr mai mic de task-uri de dimensiune
mai mare

• Subarborii obținuți prin divide and conquer determină task-uri mai mari.
• Proiectarea este abstractă deoarece problema alocării task-urilor la procesoare rămâne
nerezolvată
• Soluție: definim cate un task pentru fiecare procesor.
• Scopurile aglomerării: (1) reducerea comunicării; (2) păstrarea flexibilității în ceea
ce privește scalabilitatea și alocarea task-urilor; (3) reducerea costurilor de
dezvoltare software.
Aglomerarea: Structura fluture (en.,
butterfly)
• log N nivele;
• fiecare task primește date de
la două task-uri, efectuază o
singură operație de adunare
și transmite rezultatul către
doup taskuri aflate la nivelul
superior
• Suma celor N valori este
efectuată în log N pași.
• Suma completă este
determinată de fiecare task
al nivelului superior.
Aglomerarea: Verificarea corectitudinii
proiectării
• S-au redus costurile de comunicare prin creșterea localizării?
• Dacă nu, trebuie adaptată strategia de aglomerare.
• Au apărut procesări replicate?
• Dacă da, beneficiile replicarii sunt mai mari decât costurile? Care este influența
dimensiunii problemei și a numărului de procesoare?
• Trebuiesc replicate date?
• Dacă da, este compromisă scalabilitatea algoritmului prin limitarea dimensiunii
problemei sau a numărului de procesoare?
• Am obținut task-uri cu costuri similare de procesare și comunicare?
• Cu cât sunt mai mari task-urile definite cu atat trebuie sa fie mai similare.
• Dacă avem un task per procesor atunci acestea trebuie să aibă costuri aproximativ
identice.
Aglomerarea: Verificarea corectitudinii
proiectării (cont.)
• Numărul de task-uri scalează cu dimensiunea problemei?
• Dacă nu, atunci algoritmul nu va putea rezolva probleme mai mari pe sisteme cu
mai multe procesoare.
• Au fost eliminate posibilități de execuție concurentă?
• Un algoritm cu concurență insuficientă poate deveni cel mai eficient dacă ceilalți
algoritmi au costuri excesiv de mari de comunicare.
• Alegerea se face prin evaluarea performanțelor.
• Mai poate fi redus numărul de task-uri fără a produce dezechilibrarea
încărcării, creșterea costurilor de dezvoltare sau reducerea scalabilității?
• Care sunt costurile de modificare a versiunii secvențiale pentru obținerea
paralelismului?
Alocarea
• Cel de-al 4-lea si ultimul pas al proiectării
• Specifică unde se execută fiecare task
• Nu nu are sens în sistemele cu un singur procesor sau pe sistemele cu
memorie partajată. Acestea au mecanisme automate de planificarea
(en., schedule).
• Este o problemă dificilă care trebuie adresată explicit în procesul de
proiectare al algoritmilor paraleli.
• Are ca scop minimizarea timpului total de execuție.
Alocarea (cont.)
• Există două strategii pentru minimizarea timpului total de execuție
• Alocăm task-urile care se pot executa concurent pe procesoare diferite .
• scopul este concurența crescută.
• Alocăm task-urile care comunică frecvent pe același procesor
• scopul este localizarea crescută.
• Aceste strategii pot intra în conflict!
• Si atunci este nevoie de compromis.
• Mai mult, resursele limitate pot diminua numărul de task-uri care se pot aloca
unui singur procesor.
Alocarea (cont.)
• Problema alocarii este NP-complete, adică nu există un algoritm
polinomial (i.e., en. computationally tractable) care să evalueze
compromisurile pe caz general.
• Totuși:
• Există strategii specializate, euristici și clase de probleme care conduc la
performanță.
Alocarea
Context: număr fix de task-uri; task-uri de dimensiune
egală; comunicare structurată locală și globală.
Alocarea eficientă este directă: alocăm task-urile astfel
încât să minimizăm comunicarea între procese.
- Avem 6 procesoare
- Se alocă fiecărui procesor aceeași cantitate de procesat
- Se minimizează comunicarea între procesoare.
Alocarea
• Dacă avem dimensiuni variabile/diferite ale nevoilor de procesare, și/sau
comunicare nestructurată atunci alocarea poate să nu fie evidentă.
• În această situație este nevoie de algoritmi pentru balansarea încărcării care să
determine alocarea eficientă. De obicei, determinarea este euristică.
• Timpul necesar rulării acestor algoritmi trebuie luat în calcul.
• Algoritmii probabilistici de balansare aduc de multe ori costuri suplimentare mai
mici decât cei care se bazează e structura inițială a soluției.
• Algoritmii dinamici de balansare sunt utili atunci când numarul de task-uri,
dimensiunea procesării necesare sau cenesitățile de comunicare se schimbă dinamic
în timpul execuției.
• Algoritmii de programare a task-urilor (en. Task scheduling) sunt folositi în situațiile
descompunerii funcționale, când multe task-uri mici necesită coordonare la
începutul și la sfârșitul execuției.
Alocarea: Algoritmi de echilibrare a încărcării
• Mai sunt cunoscuți ca algoritmi de partiționare.
• Tehnica bisecțiunii recursive (en. recursive bisection)
• Partiționează un domeniu (ex., un grid finit) în subdomenii de cost aproximativ egal,
încercând să minimizeze costurile de comunicare (i.e., canalele de comunicare care
traversează granițele).
• Abordarea clasică este divide and conquer
• O primă partiționare divide domeniul în două subdomenii
• Partiționarea recursivă continuă până când numărul de subdomenii este egal cu nbumărul de
task-uri dorit.
• Tehnica bisecțiunii recursive coordonate se aplică gridurilor neregulate
• Tehnica bisecțiunii recursive de tip graf se aplică grid-urilor cu structură graf.
• Se identifică două extremități ale grafului
• Fiecare nod este alocat extremității apropiate
Alocarea: Algoritmi de echilibrare a încărcării
(cont.)
• Algoritmi locali
• Tehnicile anterioare necesită conoașterea stării globale a stării în care se află
procesarea
• Algoritmii locali de echilibrare folosesc numai informațiile obținute de un
număr mic de procesoare vecine (i.e., abordare greedy).
• Ex. Dacă procesoarele sunt organizate intr-un mesh (i.e., plasă) acestea
compară starea de încărcare cu cea a vecinilor și decid să tranfere execuția de
task-uri dacă diferența dintre încărcări depășește un anumit prag (en.,
threshold).
• Cum putem detrmina pragul optim? Experimental!
Alocarea: Algoritmi de echilibrare a încărcării
(cont.)
• Algoritmi probabilistici
• Abordare simplă care alocă task-urile în mod aleator procesoarelor.
• Dacă numarul de task-uri este mare atunci fiecare procesor va avea alocată
aproximativ aceeași catitate de procesare.
• Avantaje: cost mic și scalabilitate.
• Dezavantaje: este nevoie de comunicare, iar echilibrarea devine acceptabilă
dacă numărul de task-uri este mult mai mare decât numarul de procesoare.
• Strategia este bună atunci când avem nevoie de puțină comunicare între
task-uri.
Alocarea: Algoritmi de programare a
task-urilor (en., scheduling)
• Sunt utili atunci când descompunerea funcțională produce multe task-uri,
fiecare dintre aceste având cerințe mici/slabe de localizare.
• O mulțime (en., pool) distribuită/centralizată de task-uri este creată și
administrată.
• Aici este adăugat un task nou.
• De aici este luat un task pentru a fi alocat unui procesor.
• Task-urile devin structuri de date reprezentând ”problemele” care trebuie rezolvate
de o mulțime de procesoare.
• Strategia de alocare/programare reprezintă un compromis între cerințe
conflictuale ale task-urilor (acestea doresc rularea independentă pentru a
reduce costurile de comunicare) și starea generală a procesării (necesară
balansării).
Alocarea: Algoritmi de programare a task-urilor
• Structura manager/worker
• Worker = face cereri repetate
și rezolvă probleme alocate
de manager.
• Poate trimite managerului
task-uri noi! ... Iar managerul
le va aloca altor worker-i.
• Manager = gestionează o
mulțime de probleme (p) și
răspunde cererilor venite de
la worker-i.
Alocarea: Algoritmi de programare a
task-urilor
• Structura ierarhizată manager/worker
• Împarte worker-i în mulțimi disjuncte, fiecare cu un sub-manager
• Worker-ii cer task-uri de la sub-manageri, care comunică regulat cu
managerul și cu alți sub-manageri pentru a balansa încărcarea procesoarelor
de care răspund.
• Structuri descentralizate
• Nu există manager. Fiecare procesor administrează o mulțime separată de
task-uri.
• Practic, mulțimea de task-uri devine o structură de date distribuită care poate
fi accesată de task-uri diferite în mod asincron.
• Politici de acces
• Un worker poate face o cerere la un număr limitat predefinit de ”vecini”
Alocarea: Verificarea corectitudinii proiectării
• Ați luat în considerare un algoritm de alocare bazat pe crearea și
distrugerea dinamică a task-urilor?
• Algoritmul poate fi simplu dar demonstrarea performanței este dificilă.
• În situația în care utilizați o metodă de balansare centralizată, ati verificat
ca managerul nu reprezintă el însuși o gâtuire (en., bottleneck)?
• Puteți reduce costurile de comunicare prin transmiterea de pointeri către task-uri,
în loc să transmiteți task-ul însuși (sau toate datele) către manager.
• În situația în care utilizați o metodă de balansare dinamică, ati evaluat mai
multe strategii/variante?
• Variantele probabilistice sunt simple si trebuie avute în vedere.
• În situația în care utilizați o metodă de balansare probabilistică, aveti un
număr suficient de mare de task-uri pentru a asigura o balansare adecvată?
• De obicei, trebuie sa aveți de 10 ori mai multe task-uri decât procesoare.
Concluzii
• Descompunerea
• NU depinde de arhitectura sistemului
• Trebuie să permită concurența.
• Aglomerarea
• NU depinde de arhitectura sistemului.
• Trebuie să echilibreze încărcarea și să limiteze comunicarea.
• Alocarea
• Depinde de arhitectura sistemului.
• Grupează procesele care au legătură între ele pe procesoare.
• Exploatează localizarea în situația unei rețele.
• Orchestrarea generală a aplicației [NU depinde de arhitectura sistemului]
• Reduce comunicarea prin localizare
• Reduce serializarea rezurselor partajate
• Planifică task-urile pentru a satisface dependințele cât mai din timp.
Concluzii (cont.)
• Orchestrarea generală a aplicației
• Memoria partajată
• Datele private și partajate sunt explicit declarate/separate
• Sincronizarea se realizează prin operații atomice asupra datelor partajate
• Sincronizarea este explicită și distincă față de comunicare
• Transmiterea mesajelor
• Are nevoie de distribuirea datelor între memoriile locale
• Nu există date explicit partajate
• Comunicarea este explicită
• Sincronizarea este asigurată implicit de comunicare.
Open MP
• Este o mulțime de
directive compilator
și funcții de biblotecă
pentru programarea
paralelă.
• Simplifică
dezvoltarea
aplicațiilor cu fire de
execuție îm
C/C++/Fortran
Sintaxa OpenMP
• Cele mai multe constructe în OpenMP sunt directive compilator
#pragma omp construct [clause [clause]…]

#include<omp.h> //OpenMP include file


void main(){
// parallel region with three threads
#pragma omp parallel num_threads(3)
{// Start al codului paralel

// Runtime library function to return a thread ID


int tid = omp_get_thread_num();
printf("Hello world from thread = %d \n",tid);
if(tid == 0){
Folosirea directivelor
In Visual Studio activați suportul OpenMP:
https://msdn.microsoft.com/de-de/library/fw509c3b(v=vs.120).aspx

In gcc trebuie să aveti -fopenmp pentru linking și compilare


Cum interacționează firele în OpenMP?
• OpenMP folosește un model multi-thread cu memorie partajată
• Firele comunică prin partajarea variabilelor
• Partajarea neintenționată/întâmplatoare a datelor/variabilelor
determină apariția situațiilor de *data race* sau *race conditions*
• *data race* = această situație apare atuci când programul are raspunsuri
diferite în funcție de ordine de planificare/executare a firelor.
• Controlul zonelor de *data race* se face prin sincronizare.
• Sincronizarea este costisitoare, așadar:
• Modul de acces la date trebuie făcut astfel încât nevoia de sincrinizare să fie
minimă.
Modelul OpenMP
• Paralelismul Fork-Join
• Firul Master creează fire de excuție la nevoie.
• Paralelismul este adăugat incremental până când se obțin performanțele necesare
• Programul secvențial se transformă în program paralel
• Crearea firelor
omp_set_num_threads(4); // funcție care cere crearea unui anumit număr de fire.
• Declararea regiunilor paralele
#pragma omp parallel num_threads(4)
{ // instrucțiune care declară o regiune paralelă care cere crearea de 4 fire
} // fiecare fir execută o copie a codului din blocul declarat
Modelul OpenMP
O singură copie vectorului A este
partajată de toate firele!
double A[1000]; Comportamentul este similar cu
#pragma omp parallel num_threads(4) pthread_t tid[4];
{ for (int i = 1; i < 4; ++i)
int ID = omp_get_thread_num(); pthread_create (
myFunction(ID, A); tid[i],0,thunk, 0);
} ToDo();// the logic
printf(“Gata!\n”); for (int i = 1; i < 4; ++i)
pthread_join (tid[i]);
Sincronizarea in OpenMP
Sincronizarea este folosită pentru a impune constângeri referitoare la
ordinea de acces și pentru a proteja accesul la datele partajate.
• Bariere: Fiecare fir așteaptă la barieră până când ajung toate firele.
• Excludere mutuală: Definim o bucată de cod care poate fi executată
de un singur fir la un moment dat.
• Sincronizare de nivel înalt
• Critical
• Atomic
• Barrier
• Advanced: order, flush și locks.
Sincronizarea cu barrier
Barrier: fiecare fir așteaptă până când toate firele ajung la barieră.
#pragma omp parallel
{
int id = omp_get_thread_num();
A[id] = doSomething(id);
#pragma omp barrier
B[id] = doSomethingElse(id, A);
}
Sincronizarea cu critical
• Excluderea mutuală: Numai un fir poate intra în secțiunea critică.
float res;
#pragma omp parallel
{ float B; int i, id, nthrds;
id = omp_get_thread_num();
nthrds = omp_get_num_threads();
for(i=id;i<niters;i+=nthrds){
B = big_job(i);
#pragma omp critical ;// firele își așteaptă rândul; numai un fir poate apela small_job()
res += small_job (B);
}//end for
}
Sincronizarea cu atomic
• Atomic oferă/implementează excludere mutuală dar se aplică numai la
updatarea unei locații de memorie.

#pragma omp parallel


{
double tmp, B;
B = DOIT();
tmp = big_ugly(B);
#pragma omp atomic
X += tmp;
}
Alte constructe
• Loop worksharing construct împarte iterațiile din buclă între fire.
#pragma omp parallel
{
#pragma omp for
for (I=0;I<N;I++){
COOL_STUFF(I);// Variabila I este implicit ”privată” pentru
// fiecare fir.
}//end for
}
Alte constructe (cont)
• Intrucțiunea schedule specifică modul în care iterațiiole din buclă sunt
alocate la fire:
• schedule(static [,chunk]);// Distribuie fiecărui blocuri de idetații de dimensiune
“chunk”.
• schedule(dynamic[,chunk]);// Fiecare fir preia “chunk” iterații din coadă.
• schedule(guided[,chunk]);//Firele preiau dinamic blocuri de iterarții.
• schedule(runtime);// Planificarea și dimensiunea chunk-ului este luată din variabila
de mediue OMP_SCHEDULE.
• schedule(auto);//Planificarea este la dispoziția mediului.
Task-uri în OpenMP
• Task-urile sunt alcătuite din
• Codul care se execută
• Datele din medie (en., data environment)
• Variabile de control intern (en., internal control variables:ICV)
• Firele execută fiecrae task.
• Sistemul (i.e., runtime system) decide când se execută task-urile
• Task-urile pot fi amânate sau executate imediat
Referințe
• https://www.openmp.org/resources/tutorials-articles/
• https://www.openmp.org/wp-content/uploads/Intro_To_OpenMP_
Mattson.pdf
Algoritmi paraleli si
distribuiți
7. Algoritmi de sortare în paralel
+ CUDA
Sortarea în paralel
• Utilitatea
• Sortarea este în general o operație des folosită în aplicații.
• Implicație: Permite căutare binară (rapidă!) in O(log n)
• Scopul
• Obținerea accelerării la sortarea unui vector de valori folosind n procesoare.
• Accelerarea
• Cei mai buni algoritmi secvențiali au complexitate O(n log n)
• Cea mai mare accelerare posibilă a unui algoritm paralel care utilizează n
procesoare este O(log n) = O(n log n)/n
Algoritmi de sortare
• Task-ul este să aranjăm în ordine o colecție neordonată
• Nu este neaparat mulțime: putem avea duplicate
• Elementele nu sunt neaparat numere, putem avea puncte în plan sau alte
obiecte
• Raspunsul este practic o permutare a elementelor
• Metode bazate pe comparații
• Se compară perechi de elemente și se schimbă între ele
• O(n log n)
• Metode care NU se bazează pe comparații
• Bucket sort, counting sort, radix sort
Sortarea în paralel
• Unde se află elementele care se sortează?
• Trebuie să fie distribuite între toate procesele.
• Ordinea obținută nu trebuie să depindă de ordinea de execuție a proceselor.
• Cum sunt efectuate comparațiile?
• Dacă avem un element per proces atunci comunicarea între procese va fi
semnificativă (va domina) timpul de execuție: compară-schimbă.
• Dacă avem mai multe elemente per proces atunci va trebui efectuata
operația împarte (en., split): compară-împarte.
• Vine ca abordare alternativă (i.e., distinctă și contrastantă) cu
algoritmii de sortare care folosesc memoria partajată.
Proiectarea algoritmilor de sortare în paralel
(V1) Bazată pe un algoritm de sortare secvențial existent
• Încearcă să utilizeze toate resursele disponibile.
• Poate transforma un algoritm secvențial slab într-un algoritm paralel
rezonabil de rapid
• Ex. Bubble sort -> Bubble sort parallel (i.e., odd-even)

(V2) Abordare complet nouă


• Algoritm nou proiectat de la zero
• Dificil de dezvoltat
• Uneori obține performanțe mai bune (i.e., numai pentru o anumită
problemă cu constrângeri specifice referitoare la input, distribuții, data
types, etc.).
Sortarea în paralel (cont.)
• Un element per proces: compară-schimbă

Pas 1. Schimbă Pas 2. Compară


Sortarea în paralel (cont.)
• Mai multe elemente per proces: compară-împarte

Pas 1. Schimbă

Pas 2. Compară Pas 3. Împarte


Rețele de sortare
• Rețele care sortează n elemente în mai puțin de O(n log n)
• Elementul/nodul principal al rețelei este comparatorul
• Comparatoarele sunt conectate în paralel și permută elemente

Comparator crescător

Comparator descrescător
Proiectarea rețelei de sortare
• Sortarea se realizează în etape (i.e., pe nivele)
• Comparatoarele sunt conectate în rețea
• Raspunsul ultimului nivel
este lista sortată
• Timpul este O(log 2n)
Sortarea bitonică (en., bitonic sort)
• Secvență bitonică
• Are elementele {a0, a1, …, an-1} unde
• {a0, a1, …, ai} sunt în ordine crescătoare
• {ai, ai+1, …, an-1} sunt în ordine descrescătoare

• Secvența bitonică trebuie obținută dintr-o secvență neordonată


• Se pornește de la o secvență de dimensiune 2 care este implicit bitonică

• Din secvența bitonică se obține apoi lista sortată


Sortarea bitonică: variantă secvențială
• Problema: Avem o secvență
bitonică și dorim să o
ordonăm.
• Abordare: divide and conquer
• Operația compara-și-schimbă
mută valorile mai mici în
stânga, iar valorile mai mari în
dreapta.
• Această operație se aplică
recursiv până când secvența
devine ordonată.
Sortarea bitonică: variantă secvențială (cont.)
Ultimele două faze ale algoritmului necesare
n = lungimea secvenței bitonice pentru obținerea secvenței sortate.
log n = numărul de faze necesare
T(n) = log(n) + T(n/2)
T(n) = log(n) + log(n)-1 + log(n)-2 + ... +1 =
log(n) * (log(n) + 1) /2
Fiecare fază efectuează n/2 comparații.
În total sunt O(n*log2(n)) comparații.
Pentru P=n (n procesoare),
T(n)= O(n*log2(n)) /n = O(log2(n))
Sortarea
bitonică:
1.
crearea
secvenței
bitonice
[conectarea
procesoarelor]
Sortarea
bitonică:
2.
crearea
secvenței
sortate
[conectarea
procesoarelor]
Sortarea cu un array de procesoare
• Fiecare procesor este conectat la ambii vecini (conexiunile sunt
bidirecționale, ca într-o lista dublu înlănțuită)
• Toate procesoarele partajează acleași ceas (sincronism)
• La fiecare tact, procesoarele primesc datele de intrare de la vecini,
efectuază calculul, transmit răspunsul către vecini și updatează
memoria locală.
Sortarea cu un array de procesoare
• Logica fiecărui procesor
• Fiecare procesor stochează valoarea cea mai mică pe care a văzut-o
• Valoarea inițială implicită este ”*”, care este prin convenție interpretată ca
fiind mai mare decât orice valoare posibilă din
input (i.e., interpretarea poate fi asimilată cu lipsa
de semnal sau nimic)
• Cand procesorul primește valoarea Y de la vecinul
din stânga, acesta păstrează valoarea mai mică
dintre Y si valoarea curentă Z, și transmite valoarea
mai mare catre vecinul drept.
Sortarea cu
un array de
procesoare:
Exemplu
Shear sort
• Input: o matrice n x m, nesortată
• Output: matricea n x m sortată șerpuit
Pașii algoritmului
• For i = 1, ... 2 log n + 1
• If (i este impar) : Faza rânduri: Sortăm fiecare rând folosind sortarea odd-even
• Rândurile impare sunt sortate de la stânga la dreapta.
• Rândurile pare sunt sortate de la dreapta la stânga.
• If (i este par): Faza coloane: Sortăm fiecare coloană folosind sortarea
odd-even
Shear sort:
Exemplu
Shear sort
Lemă:
ShearSort trece de log n+1 ori prin toate rândurile (if-ul impar) și de
log n ori prin toate coloanele (if-ul par) pentru a sorta n*m valori ale
unei matrici M(n, m) în ordine șerpuită.

Demonstrație:
- cu ajutorul lemei de sortare 0-1.
Lema de sortare 0-1
Fie M(n,m) o matrice numai cu valori de 0 sau 1.
Există 3 tipuri de rânduri: numai cu 0, numai cu 1, și mixed (și 0 și 1).
Obs: Inițial, matricea M poate conține (în cazul cel mai defavorabil) n
rânduri mixed.
Matricea răspuns (sortată șerpuit) conține cel mult un rând mixed.
Propoziție:
După fiecare etapă (iterație din for = o fază rânduri și o fază
coloane) numărul de rânduri mixed se reduce cu jumătate.
Lema de sortare 0-1
Lemă: Dacă un algoritm de tipul compară-schimbă reușește să sorteze
secvențe de elemente care au numai valorișe 0 sau 1, atunci acest algoritm
va reuși să sorteze și secvențe de elemente de valori oarecare.
Demonstrație:
Fie algoritmul A și un input de dimensiune N.
Presupunem că A este corect pe orice input cu valorile 0 și 1.
Presupunem că A nu rezolvă input-ul [x1, x2, …, xN] cu răspunsul [y1, y2, …,
yN]. Asta înseamnă că y1 ≤ y2 ≤ ... ≤ym > ym+1pentru un anumit m.
Fie funcția F cu F(x) = 0 dacă x < ym , altfel F(x) = 1.
Deoarece F păstrează ordinea ≤, răspunsul algoritmului A pentru [F(x1), …,
F(xN)] este [F(y1), …, F(yN)] , și este de forma […, 1, 0, …] deoarece ym > ym+1.
Contradicție.
Lema de sortare 0-1.
Comportamentul algortmului.
Lema de sortare 0-1.
Comportamentul algortmului.
• Dacă avem N rânduri mixed atunci vom avea mai puțin de N/2 rânduri
mixed după primul pas al sortării coloanelor.
• Fiecare pereche de rânduri mixed detrmină prin sortare cel puțin un
rând pur (numai 0 sau numai 1)
Bubble sort paralel
• Ușor paralelizabilă pentru algoritmul clasic de complexitate O(n2)
• Bubble sort secvențial
• O(n) timp pentru fiecare parcurgere
• O(n) parcurgeri
• Putem să o paralelizăm?
• Bubble sort în paralel = Sortarea odd-even (i.e., par-impar)
• Compară și schimbă perechile de indecși pari și impari
• După n parcurgeri elementele sunt sortate
• Putem să o paralelizăm?
Sortarea odd-even: Exemplu
index 1 2 3 4 5 6 7 8
Valoare 3 2 3 8 5 6 4 1 //odd
2 3 3 8 5 6 1 4 //even
2 3 3 5 8 1 6 4 //odd
2 3 3 5 1 8 4 6 //even
...
1 2 3 3 4 5 6 8 //even
// secvența sortată
Sortarea odd-even: Algoritm
1. procedure ODD-EVEN PARALEL(n)
2. begin
3. id := process’s label
4. for i := 1 to n do
5. begin
6. if i is odd then
7. if id is odd then
8. compare-exchange min(id + 1);
9. else
10. compare-exchange max(id - 1);
11. if i is even then
12. if id is even then
13. compare-exchange min(id + 1);
14. else
15. compare-exchange max(id - 1);
16. end for
17. end ODD-EVEN PARALEL
Sortarea odd-even: Complexitate
• Optimizare (asemeănătoare ca la varianta secvențială)
• Se defineste variabila partajată sorted (i.e., un flag), care este inițializată cu
valoarea true la începutul fiecărei iterații (formată din cele două faze)
• Daca un singur procesor trebuie să efectueze un swap atunci sorted := false.
• În interiorul buclei principale
• Faza odd utilizează/necesită n/2 procesoare/comparații
• Faza even utilizează necesită n/2 procesoare/comparații
• Bucla principală for conține n pași.
• Complexitatea rulării în paralel este O(n).
• Quicksort secvențial are complexitatea O(n log n)
Algoritmul Quicksort
• Complexitatea medie este O(n log n)
• Strategia: divide-and-conquer
• Împarte șirul în subsecvențe astfel încât fiecare element din din prima
subsecvență este mai mic sau egal cu fiecare element din cea de-a doua
subsecvență.
• Pentru împărțirea secvenței se folosește un pivot.
• Pasul combine (i.e., conquer) aplică recursiv algoritmul.
• Putem sa paralelizăm algoritmul?
Quicksort secvențial
Algoritmul are concurență intrinsecă, deci
reprezintă un candidat bun pentru paralelizare:
• - Listele cu valori mai mici și mai mari pot fi
sortate în paralel.
Algoritmul Quicksort secvențial
1. procedure QUICKSORT (A, q, r )// q este pozitia pivotului si r este dimensiunea lui A
2. begin
3. if q < r then
4. begin
5. x := A[q]; // x este valoarea pivotului
6. s := q; // s este poziția pivotului
7. for i := q + 1 to r do // determinăm s, poziția finală a pivotului
8. if A[i] ≤ x then
9. begin
10. s := s + 1;
11. swap(A[s], A[i ]);
12. end if
13. swap(A[q], A[s]); //plasăm pivotul pe poziția finală
14. QUICKSORT (A, q, s);
15. QUICKSORT (A, s + 1, r );
16. end if
17. end QUICKSORT
Quicksort paralel
• Presupunem că ne aflăm în cazul unui sistem cu memorie distribuită
• Fiecare proces gestionează un segment din lista nesortată
• Lista nesortată este distribuită în mod egal între procese
• Rezultatul algoritmului:
• Fiecare listă a fiecărui proces este sortată
• Ultimul element lin lista procesului i este mai mic decât primul element al
listei procesului i+1
Quicksort paralel cu memorie distribuită
• Alegem aleator un pivot de la unul dintre procese și îl transmitem către toate
celelalte procese (en., broadcast).
• Fiecare proces împarte lista sa nesortată în două subliste:
• Cu valori mai mici sau egale cu pivotul și cu valori mai mari decât pivotul.
• Fiecare proces din prima jumătate a proceselor trimite sublista cu valori mici către un
proces partener tot din prima jumătate a proceselor și primește la schimb sublista cu
valori mai mari.
• Astfel, sublistele cu valori mari au valori mai mari decât pivotul, oar sublistele cu valori mici au
numai valori mai mici decât pivotul.
• Procesele se împart iar în două grupuri și algoritmul continuă recursiv.
• După log P apeluri recursive, fiecare proces deține o listă nesortată de valori complet
disjuncte de valorile deținute de celelalte procese.
• Cea mai mare valoare a procesului i este mai mică decât cea mai mică valoare a procesului i+1.
• Fiecare proces folosește quicksort secvențial pentru a sorta lista
Exemplificare
intuitivă și analiză
Balansarea încărcării este dificil de
realizat
- Dacă pivotul nu este ales
convenabil (i.e., valoarea
mediană) atunci lista nu se va
împărți în subliste de aceeași
dimensiune.
- Determinarea valorii mediane
are complexitate mare în
sostemele distribuite.
- Soluția este să alegem ca pivot o
valoare cât mai apropiată de
valoarea mediană.
Quicksort paralel cu memorie partajată
Pasul 1:

Selectarea pivotului

După
rearanjarea locală
După
rearanjarea globală
Quicksort paralel cu memorie partajată
(cont.)
Pasul 2:

Selectarea pivotului

După
rearanjarea locală
După
rearanjarea globală
Quicksort paralel cu memorie partajată
(cont.)
Pasul 3:

Selectarea pivotului

După
rearanjarea locală
După
rearanjarea globală
Quicksort paralel cu memorie partajată
(cont.)
Pasul 4:

După
rearanjarea locală
Soluția:
Sample Sort
• Algoritmul Sample sort este util atunci când elementele (valorile) NU sunt
uniform distribuite într-un interval.
• Creăm m buckets și sortăm fiecare bucket cu quicksort.
• Extragem un eșantion de dimensiune s
• Sortăm eșantioanele și alegem m-1 elemente ca splitters.
• Împărțim elementele în m buckets și apelăm quicksort
• Dacă elementele (valorile) sunt uniform distribuite într-un interval atunci
putem folosi algoritmul Bucket sort
• Se crează m buckets (= cu numărul de valori distincte) și se plasează elementele în
bucket-ul corespunzător
• Complexitatea este O(n log (n/m))
• Dacă m=n, folosim valoarea ca index pentru a obține timp O(n)
Algoritmul Sample Sort paralel
Pas 1. Se împarte lista inițială nesortată în P segmente egale. Fiecare segment
se sortează cu ajuorul quicksort. Din fiecare segment se selectează elementele
care se află la pozițiile 0, n/P2, 2n/P2, ..., (P-1)n/P2
Aceste valori reprezintă eșantionul reprezentativ al segmentelor sortate local.
Pas 2. Se soretază eșantionul obținut la pasul anterior. La acest pas selectăm
P-1 valori pivot din eșantionul reprezentativ. Cele P-1 valori sunt transmise
către toate procesele/segmentele. Fiecare proces împarte lista sa sortată în P
subliste disjuncte corespunzătoare valorilor pivor primite.
Pas 3. Fiecare proces i păstrează partiția sa i și transmite partiția j procesului j
pentru toate valorile j diferite de i.
Pas 4. Fiecare proces combină cele P partiții pentru a obține lista ordonată.
Sample Sort paralel: exemplu
Valorile inițiale

Sortare locală (qs) și


Selectarea eșantionului

Sortarea eșantionului

Selectarea spliter-ului
global

Atribuirea finală
Merge Sort
• Strategia este divide-and-conquer
• Împarte (en., divide) vectorul de elemente în două părți
• Aplică aceeași strategie pentru fiecare parte definită anterior
• Atunci când două părți ajung sa fie ordonate ele trebuie unite (en., merge,
conquer) într-un vector ordonat care contine elementele celor două părți.
• Complexitatea medie O(n log n)
• T(n) = b pentru n=1
2T (n/2) + b*n pentru n>1 //Rezolvați relatia de recurență!

• Merge Sort în paralel: Definim două task-uri:


• Divide și
• Conquer
Merge Sort (cont.)
• Divide
• Împarte vectorul de elemente la
două procesoare distincte
• Paralelismul este limitat
aproape de rădăcină.
Merge Sort (cont.)
• Conquer
• Input: doi vectori sortați
• Output: un vector sortat
care conține toate
elementele celor doi
vectori di input.
Algoritmul Merge Sort paralel
Algoritm: MergeSort (A)
if (|A| = 1) then return A
else
#Do in parallel
L := MergeSort (A[0 … |A|/2])
R := MergeSort (|A|/2] … |A|)
return merge (L, R)
Algoritmul Merge Sort parallel: Exemplul 1
Input: secvența 4, 3,2, 1 și avem la dispoziție 2 procesoare P0 și P1
P0 [4, 3, 2, 1]
P0 [4, 3] P1 [2, 1]
[4] [3] [2] [1]
P0 [3, 4] P1 [1, 2]
P0 [1, 2, 3, 4]
Algoritmul Merge Sort parallel: Exemplul 2
Algoritmul Merge Sort parallel (cont.)
Idea principală: Paralelizarea procesări subproblemelor
Paralelizarea maximă se obține atunci când avem câte un procesor
pentru fiecare nod la fiecare nivel din arbore.
Complexitatea
• Algoritmul merge sort secvențial, O(n log n)
• În paralel, cu n procesoare
• log n pentru divide
• log n pentru merge
• log n + log n = 2 log n
• O(log n)
CUDA: Scurtă prezentare
• CUDA (Compute Unified Device Architecture) este o platformă de calcul paralel
care pune la dispoziție API creat de Nvidia pentru dezvoltarea de aplicații care
utilizează GPU (graphics processing unit).
• Platforma CUDA reprezintă un nivel software care permite accesul și folosirea
directă a GPU-ului.
• Limbaje de programare folosite: C, C++, Fortran.
• Se permite dezvoltarea mai ușoară de aplicații paralele comparativ cu Direct3D și OpenGL
folosite în procesarea grafică.
• CUDA-powered GPUs supportă framework-uri ca OpenACC sau Open CL
CUDA: Beneficiile utilizării GPU
• GPU are un debit (en., throughput) mult mai mare de instrucțiuni executate
și o lățime de bandă mult mai mare decât CPU pentru costuri și putere
consumată similare.
• Capabilitățile eferite de GPU comparativ cu CPU sunt datorate scopurilor
diferite pe care le au în vedere proiectanții.
• CPU: scopul principal este să execute o secvență de operații într-un fir de execuție.
• GPU: scopul principal este să execute mii de secvențe de operații în paralel.
Performața unui singur fir de execuție este mai mică, dar debitul (i.e., cantitatea de
date procesată) este mult mai mare.
• GPU este specializat pentru executarea de multe operațiuni în paralel astfel încât cei
mai mulți tranzistori sunt dedicați procesarii de date și NU gestionării de cache-uri
sau controlul fluxului aplicației.
CPU vs. GPU
CPU vs. GPU
• GPU:
• Defică mult mai mulți tranzistori pentru procesare (i.e., operații FLP) ceea ce
ajută mult procesările paralele.
• Are latență mică la efectuarea calculelor deoarece nu se bazează pe cache-uri
si nici pe fluxuri complicate de calcul.
• Aplicațiile sunt de obicei un mix între părți secvențiale și paralele
• Părțile secvențiale sunt executate de CPU
• Părțile paralele sunt executate de GPU
• Astfel încât de maximizează performanța generală.
Aplicații
care
utilizeaz
ă GPU
Model de programare paralel CUDA
• Provocarea este să ofere o curbă de învățare rapidă pentru
programatorii C/C++.
• Ingrediente fundamentale
• Firele de execuție
• Memoria partajată
• Sincronizarea barierelor
• Aceste ingredient oferă paralelism cu granularitate mică a datelor și
paralelismul firelor, alături de paralelismul de granularitate mare și
paralelismu task-urilor.
Model de programare paralel CUDA
// Kernel definition
Kernels reprezintă în CUDA ceea __global__ void VecAdd(float* A, float* B,
ce reprezintă funcțiile din C++. float* C)
- Atunci când sunt apelate ele se {
int i = threadIdx.x;
rulează de N ori în paralel pe N
C[i] = A[i] + B[i];
fire diferite. }
- Este definit de declarația
__global__ și de numărul de fire int main()
{ ...
CUDA care execută kernel-ul. // each of the N threads that execute
- <<<...>>> este sintaxa pentru //VecAdd() performs one pair-wise addition.
configurarea execuției. VecAdd<<<1, N>>>(A, B, C);
}
Model de programare paralel CUDA (cont.)
// Kernel definition __global__
Ierarhia de fire void MatAdd(float A[N][N], float B[N][N],
float C[N][N])
threadIdx este un vector cu 3 {
int i = threadIdx.x;
componente. Firele pot fi int j = threadIdx.y;
identificate folosind indecși C[i][j] = A[i][j] + B[i][j];
1D, 2D sau 3D care formază }

astfel blocuri de fire (en., int main()


thread block). Acest mod de { ... // Kernel invocation with one block
//of N * N * 1 threads
lucru perimite lucrul natural int numBlocks = 1;
cu date de tip vector, matrice dim3 threadsPerBlock(N, N);
sau volum. MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
...
}
Model de programare paralel CUDA (cont.)
• Ierarhia memoriei
• Firele CUDA pot accesa date din maimulte zone de memorie în timpul
execuției.
• Fiecare fir are o zona de memorie locală privată.
• Fiecare bloc de fire (en., thread block) are o zonă de memorie partajată care
este vizibilă/accesibilă tuturor firelor din același bloc.
• Toate firele au acces la memoria globală.
• Programarea eterogenă (secvențială și paralelă)
• Kernel-urile se execută pe GPU
• Codul C++ (secvențial) se execută pe CPU.
CUDA: Dezvoltarea aplicațiilor
• Compilarea cu nvcc;// pentru codul care conține C++ Language Extensions
// (i.e., __global__, __device__, etc.)
• Compilarea offline și just-in-time
• Compatibilitatea binară
• Compatibilitatea PTX
• Compatibilitatea aplicației
• CUDA Runtime
• Inițializare
• Memoria și managementul memoriei
• Memoria partajată
• Etc.
CUDA: Referințe
https://docs.nvidia.com/cuda/cuda-c-programming-guide/
https://docs.nvidia.com/cuda/cuda-c-programming-guide/#introduction
https://docs.nvidia.com/cuda/cuda-c-programming-guide/#programming-model
https://docs.nvidia.com/cuda/cuda-c-programming-guide/#programming-interface
Algoritmi paraleli si
distribuiți
8. Algoritmi de înmulțire a matricilor
+ openACC
Tipuri de paralelism (review)
• Paralelismul datelor
• Toate procesoarele execută același cod pe date diferite
• Paralelismul task-urilor
• Procesoarelor le sunt alocate task-uri care execută cod diferit
• Modele de execuție în paralel
• Paralelismul datelor
• Pipeline (i.e., producător-consumator)
• Graf de task-uri
• Pool de task-uri
Paralelismul datelor
• Datele/domeniul este descompus și alocat procesoarelor
• Procesoarele execută task-uri identice (sau foarte similare) pe datele
primite (care sunt diferite de la un procesor la altul)
• Task-urile se execută în paralel
• Echilibrarea încărcării este obținută prin partiționarea datelor
• Cantități egale de date sunt alocate tuturor procesoarelor
• Scalabilitatea paralelismului datelor
• Gradul de paralelizare tinde să crească odată cu creșterea dimensiunii problemei
• Face ca algoritmii sa fie eficienți/performanți
• SPMD (en., Single Program Multiple Data)
• Mod oportun de implementare a paralelismului datelor
• Este asociat cu execuția în paralel cu memorie distribuită
Înmulțirea unei matrici cu un vector

Înmulțirea unei matrici cu un vector (cont.)
Înmulțirea unei matrici cu un vector (cont.)

Înmulțirea unei matrici cu un vector (cont.)
Dacă avem un număr
limitat de task-uri (ex., 4)?
- Care sunt dependințele?
- Care este accelerarea?
Înmulțirea matricilor
C [nxn] = A[nxn] x B[nxn]
- Complexitatea secvențială
este O(n3)
Obs: Algoritmul lui Strassen este mai bun
decât O(n3). Se poate folosi ca și kernel
serial în cadrul algoritmilor paraleli
complecși.
- Partiționare la nivel de rând
- N task-uri
- Partiționare la nivel de bloc
- N * N/B task-uri
- Partea gri arată partajarea
datelor din matricea B
Înmulțirea matricilor

Înmulțirea matricilor

Înmulțirea matricilor
• Accelerarea și eficiența

Sp = N3 / (N3/p) = p

Ep = N3 / p(N3/p) = 1

Metoda permite obținerea de accelerare și eficiență ideale, fără a lua în


calcul costurile suplimentare cu comunicarea și memoria.
Înmulțirea matricilor

Înmulțirea matricilor

Inmulțirea matricilor: Algoritmul lui Cannon

Inmulțirea matricilor: Algoritmul lui Cannon

Inmulțirea matricilor: Algoritmul lui Cannon

Matricea inițială A Matricea inițială B


Rândul 0 este neschimbat Coloana 0 este neschimbată
Rândul 1 este deplasat cu o poziție la stânga Coloana 1 este deplasată cu o poziție în sus
Rândul 2 este deplasat cu 2 poziții la stânga Coloana 2 este deplasată cu 2 poziții în sus
Rândul 3 este deplasat cu 3 poziții la stânga Coloana 3 este deplasatăcu 3 poziții în sus
... ...
Inmulțirea matricilor: Algoritmul lui Cannon

Inmulțirea matricilor: Algoritmul lui Cannon
Exemplu pentru matrice de deminesiune 3x3
C[1,2] = A[1,0]*B[0,2] + A[1,1]*B[1,2] + A[1,2]*B[2,2]
Inmulțirea matricilor: Algoritmul lui Cannon
C[1,2] = A[1,0]*B[0,2] + A[1,1]*B[1,2] + A[1,2]*B[2,2]

- Initial trebuie ca A[1,0] și B[0,2] să


se afle pe același procesor.
- Transferă (en., shift) rândurile și
coloanele astfel încât valorile
blocurilor A[1,1] și B[1,2] să fie
aliniate.
- Asemănător se efectuază transferul
pentru A[1,2] și B[3,2].
Inmulțirea matricilor: Algoritmul lui Cannon
C[1,2] = A[1,0]*B[0,2] + A[1,1]*B[1,2] + A[1,2]*B[2,2]
Alinierea matricilor:
- Matricile trebuie inițial aliniate astfel încât
primele blocuri necesare (A[1,0] și B[0,2] ) să
se afle la acelați procesor P[1,2] care va
determina valoarea blocului C[1,2].
- Alinierea se realizează prin transferul (en.,
shift) fiecărui rând i cu i poziții la stânga.
- Același mecanism se folosește și pentru
coloane.
Inmulțirea matricilor: Algoritmul lui Cannon

Înmulțirea matricilor : Algoritmul lui Cannon
• Accelerarea și eficiența

Sp = N3 / (N3/p) = p

Ep = N3 / p(N3/p) = 1

Metoda permite obținerea de accelerare și eficiență ideale, fără a lua în


calcul costurile suplimentare cu comunicarea și memoria.
Inmulțirea matricilor: Algoritmul lui Cannon

Inmulțirea matricilor: Algoritmul lui Cannon

Inmulțirea matricilor: Algoritmul lui Fox

Algoritmul lui Fox

Algoritmul lui Fox: Descompunerea domeniului

Algoritmul lui Fox: Exmplificare intuitivă
Inițializare: A00
Elementele Aii sunt distribuite către matricea rezultat C B00 B01 B02 B03
astfel: A11
C0,0 = A0,0* B0,0 ; C0,1 = A0,0* B0,1; C0,2 = A0,0* B0,2; C0,3 = A0,0* B0,3 B10 B11 B12 B13
A22
C1,0 = A1,1* B1,0 ; C1,1 = A1,1* B1,1; C1,2 = A1,1* B1,2; C1,3 = A1,1* B1,3 B20 B21 B22 B23
A33
C2,0 = A2,2* B2,0 ; C2,1 = A2,2* B2,1; C2,2 = A2,2* B2,2; C2,3 = A2,2* B2,3 B30 B31 B32 B33

C3,0 = A3,3* B3,0 ; C3,1 = A3,3* B3,1; C3,2 = A3,3* B3,2; C3,3 = A3,3* B3,3
Algoritmul lui Fox: Exemplificare intuitivă
(cont.)
Pasul 1: Se transmit urmatoarele elemente din A (i.e., cele din A01
dreapta diagonalei principale) către procesoarele corespunzătoare, B10 B11 B12 B13
coloanele din B sunt shiftate cu o poziție în sus, si se realizează A12
înmulțirea. B20 B21 B22 B23
C0,0 += A0,1* B1,0; C0,1 += A0,1* B1,1; C0,2 += A0,1* B1,2; C0,3 += A0,1* B1,3 A23
B30 B31 B32 B33
A30
C1,0 += A1,2* B2,0 ; C1,1 += A1,2* B2,1; C1,2 += A1,2* B2,2; C1,3 += A1,2* B2,3 B00 B01 B02 B03

C2,0 += A2,3* B3,0 ; C2,1 += A2,3* B3,1; C2,2 += A2,3* B3,2; C2,3 += A2,3* B3,3

C3,0 += A3,0* B0,0 ; C3,1 += A3,0* B0,1; C3,2 += A3,0* B0,2; C3,3 += A3,0* B0,3
Algoritmul lui Fox: Exemplificare intuitivă
(cont.)
Pasul 2: Se transmit urmatoarele elemente din A către procesoarele A02
corespunzătoare, coloanele din B sunt shiftate cu o poziție în sus, si B20 B21 B22 B23
se realizează înmulțirea. A13
C0,0 += A0,2* B2,0; C0,1 += A0,2* B2,1; C0,2 += A0,2* B2,2; C0,3 += A0,2* B2,3 B30 B31 B32 B33
A20
B00 B01 B02 B03
C1,0 += A1,3* B3,0 ; C1,1 += A1,3* B3,1; C1,2 += A1,3* B3,2; C1,3 += A1,3* B3,3 A31
B10 B11 B12 B13

C2,0 += A2,0* B0,0 ; C2,1 += A2,0* B0,1; C2,2 += A2,0* B0,2; C2,3 += A2,0* B0,3

C3,0 += A3,1* B1,0 ; C3,1 += A3,1* B1,1; C3,2 += A3,1* B1,2; C3,3 += A3,1* B1,3
Algoritmul lui Fox: Exemplificare intuitivă
(cont.)
Pasul 3: Se transmit urmatoarele elemente din A către procesoarele A03
corespunzătoare, coloanele din B sunt shiftate cu o poziție în sus, si B30 B31 B32 B33
se realizează înmulțirea. A10
C0,0 += A0,3* B3,0; C0,1 += A0,3* B3,1; C0,2 += A0,3* B3,2; C0,3 += A0,3* B3,3 B00 B01 B02 B03
A21
B10 B11 B12 B13
C1,0 += A1,0* B0,0 ; C1,1 += A1,0* B0,1; C1,2 += A1,0* B0,2; C1,3 += A1,0* B0,3 A32
B20 B21 B22 B23

C2,0 += A2,1* B1,0 ; C2,1 += A2,1* B1,1; C2,2 += A2,1* B1,2; C2,3 += A2,1* B1,3

C3,0 += A3,2* B2,0 ; C3,1 += A3,2* B2,1; C3,2 += A3,2* B2,2; C3,3 += A3,2* B2,3
Algoritmul lui Fox: Complexitate
• Algoritmul lui Fox este efficient d.p.d.v. al memoriei necesare
• Totuși, costurile suplimentare apărute din cauza comunicării sunt mai
mari decât cele necesare Algoritmului lui Cannon.
• Scalarea și distribuirea/alocarea subtask-uriloe pe procesoare
• Dimeniunile matricelor pot fi alese astfel încât numărul de subtask-uri este
același cu numprul de procesoare.
• Execuția cea mai eficientă pentru rularea în paralel se obține atunci când
topologia rețelei este un grid 2D. Astfel, subtask-ul (i,j) este alocat
procesorului pij . In această situație, accelerarea și eficiența sunt ideale
Sp = n2 / (n2/p) = p, si Ep=n2/(p*(n2/p)) = 1
fără a lua în calcul costurile suplimentare cu comunicarea și memoria
Algoritmul SUMMA (en., Scalable Universal
Matrix Multiplication Algorithm)
• Fie A[m x n] * B[k x n] = C[m x n]
Inmulțirea naivă: Dacă schimbăm ordinea?
For i = 0 to n For k = 0 to n
For j = 0 to n For i = 0 to n
For k = 0 to n For j = 0 to n
C[i,j] += A[i,k] * B[k,j] C[i,j] += A[i,k]*B[k,j]
Calculăm n2 valori (en., inner products) Calculăm n valori (en., outer products)
C[i,j] = A[i,:]*B[:,j]
Algoritmul SUMMA
• Presupunem ca avem procesoarele distribute într-un grig, P(i,j)
• Exemplu de grid 2 x 2

P(1,1) x P(1,2) = P(1,1) P(1,2) P(1,1) P(1,2)

P(2,1) P(2,2) P(2,1) P(2,2) P(2,1) P(2,2)

A B C
Dacă A[mxk] si B[kxn] => C[mxn]
- Fiecare processor va realiza k înmulțiri
Q: Cum administrăm/tratăm/realizăm comunicația?
Algoritmul SUMMA
Fiecare proces P(i,j) execută
• Pentru k = 0 la n-1
• Se transmite (broadcast) coloana k din A (a_i) rândului i [1]
• Se transmite (broadcast) rândul k din B (b_j) coloanei j [2]
• C += a_i x b_j;// outer product [3]

Van De Geijn, R. A., & Watts, J. (1997). SUMMA: Scalable


universal matrix multiplication algorithm. Concurrency: Practice
and Experience, 9(4), 255-274.
http://www.netlib.org/lapack/lawnspdf/lawn96.pdf
Algoritmul SUMMA

OpenACC: https://www.openacc.org/
• OpenACC 1.0 in November of 2011
• OpenACC 3.0 was released in November 2019
• Mod facil de a obține accelerarea aplicațiilor
• Alte variante:
• Cu ajutorul bibliotecilor: ”drop-in”
• Cu ajutorul limbajelor de programare: avem flexibilitate maximă
OpenACC
• Directivele OpenACC
• Funcționează pe GPU many-core si CPU multicore
• Reprezintă îndicii (en., hints) pentru compilator
• Compilatorul paralelizează codul
• Modul de lucru este asemănător cu OpenMP
Recomandări:
• “OpenACC will enable programmers to easily develop portable applications that
maximize the performance and power efficiency benefits of the hybrid CPU/GPU
architecture of Titan.”
--Buddy Bland, Titan Project Director, Oak Ridge National Lab
• “OpenACC is a technically impressive initiative brought together by members of
the OpenMP Working Group on Accelerators, as well as many others. We look
forward to releasing a version of this proposal in the next release of OpenMP.”
--Michael Wong, CEO OpenMP Directives Board
OpenACC
• Standardul pentru directive de lucru cu GPU
• Ușurința în utilizare: Directivele sunt ușor de folosit pentru a accelera
aplicațiile cu nevoi mari de calcul.
• Deschidere (en., open): OpenACC este standard deschis pentru directive de
lucru cu GPU. Acest aspect face ca programarea pe GPU să devină directă și
portabilă pe GPU many-core si CPU multicore.
• Putere/forță: Directivele GPU permit acces complet la puterea de procesare
paralelă a GPU.
OpenACC
Programare de nivel înalt cu acces la nivel jos
• Directivele compilator specifică proțiunile de cod paralel in C
• Compilatorul OpenACC rulează porțiunile de cod paral pe GPU și nu pe sistemul
gazdă
• Sunt portabile peste OSs, sistemele gazdă, acceleratoare și compilatoare
• Permit obținerea de programe eterogene de nivel înalt
• Fără inițializarea explicită a acceleratorului
• Fără să fie nevoie de tranferul explicit de codsau date între calculatorul gazdă și
accelerator.
• Sunt compatibile cu alte limbaje de lucru cu GPU și biblioteci
• Interoperează cu CUDA C/Fortran și alte bibliotecu GPU (i.e., CUFFT, CUBLAS,
CUSPARSE, etc.)
OpenACC
Avantaj: ”Optimizing code with directives is quite easy, especially
compared to CPU threads or writing CUDA kernels. The most important
thing is avoiding restructuring of existing code for production
applications. ”
- Developer at the Global Manufacturer of Navigation Systems
- Aspectul principal în dezvoltare se referă la specificarea/expunerea
paralelismului prin folosirea directivelor.
OpenACC: Exemplu
void saxpy(int n, float a, float *x, float *restrict y)
{
#pragma acc kernels
for (int i = 0; i < n; ++i)
Sintaxa directivelor in C:
y[i] = a*x[i] + y[i]; #pragma acc directive [clause [,] clause] …]
} …often followed by a structured code block
...
// Perform SAXPY on 1M elements
saxpy(1<<20, 2.0, x, y);
...
OpenACC: Exemplu (cont.)
Kernels: primele directive OpenACC
//Fiecare buclă se execută ca un kernel separat pe GPU.
!$acc kernels
do i=1,n
a(i) = 0.0
Kernel #1
b(i) = 1.0 Kernel =
c(i) = 2.0
end do
o funcție care rulează în paralel pe GPU

do i=1,n
Kernel #2
a(i) = b(i) + c(i)
end do
!$acc end kernels
OpenACC: Exemplu (cont.)
• Construirea de Kernels in C
#pragma acc kernels [clause …]
{ structured block }

Clauses:
if( condition )
async( expression )
// clauze care se referă la date
OpenACC: Exemplu (cont.)
• Cuvântul cheie restrict
• Reprezintă declararea intenției programatorului către compilator
• Ex., aplicat la un pointer float *restrict ptr;
• Înseamnă: ”pe durata de viață a variabilei ptr , numai aceasta sau o valoare
obținută din aceasta (ex., ptr+1) va putea fi folosită pentru a accesa
obiectul către care pointează variabila”.
• Implicații:
• Se limitează efectele de pointer aliasing.
• Compilatoarele OpenACC necesită desori folosirea cuvântului cheie restrict
pentru a determina independența
• În caz contrar compilatorul nu poate paraleliza buclele care accesează ptr.
• Dacă programatorul nu definesc corect independența => *undefined behaviour*
int main(int argc, char **argv)
OpenACC: Exemplu (cont.) {
Variabila y nu va int N = 1<<20; // 1 million floats
fi un pointer alias if (argc > 1)
#include <stdlib.h> pentru x. N = atoi(argv[1]);
void saxpy(int n, float a, float *x = (float*)malloc(N * sizeof(float));
float *x, float *restrict y) float *y = (float*)malloc(N * sizeof(float));
{ for (int i = 0; i < N; ++i) {
#pragma acc kernels x[i] = 2.0f;
y[i] = 1.0f;
for (int i = 0; i < n; ++i)
}
y[i] = a*x[i] + y[i]; saxpy(N, 3.0f, x, y);
} return 0;
}
OpenACC: Exemplu (cont.)
• Compilare și rulare în C:
pgcc –acc [-Minfo=accel] –o saxpy_acc saxpy.c

saxpy:
8, Generating copyin(x[:n-1])
Generating copy(y[:n-1])
Generating compute capability 1.0 binary
Generating compute capability 2.0 binary
9, Loop is parallelizable
Accelerator kernel generated
9, #pragma acc loop worker, vector(256) /* blockIdx.x threadIdx.x */
CC 1.0 : 4 registers; 52 shared, 4 constant, 0 local memory bytes; 100% occupancy
CC 2.0 : 8 registers; 4 shared, 64 constant, 0 local memory bytes; 100% occupancy
OpenACC: Exemplu (cont.)
• Clauze pentru date
copy ( list ) : Alocă memorie pe GPU și copiază date care se află pe
sistemul gazdă pe GPU atunci când se intră în porțiunea paralelizată, și
transferă datele înapoi pe sistemul gazdă la ieșire din zona paralelizată.
copyin ( list ) Alocă memorie pe GPU și copiază date care se află pe
sistemul gazdă pe GPU atunci când se intră în porțiunea paralelizată.
copyout ( list ) Alocă memorie pe GPU și copiază date din GPU pe
sistemul gazdă când se iese în porțiunea paralelizată.
create ( list ) Alocă memorie pe GPU dar nu copiază date.
present ( list ) Datele sunt deja prezente pe GPU.
OpenACC: alte clauze și directive
• Array shaping: atunci când nu se poate determina dimensiunea unui
array.
• Update: #pragma acc update [clause …]
Clauze:
host( list ) if( expression )
device( list ) async( expression )
• Pentru a updata date după ce o copie a acestora a fost modificată (ex.,
updatarea copiei din GPU dupa ce copia de pe sistemul gazdă a fost
modificată).
• Pentru mutarea datelor între GPU și gazda.
OpenACC: Determinarea oportunitătilor de
paralelizare
• Buclele încuibate (en., nested for loops)
• Corpurile buclelor trebuie să fie independente unele de altele
• Compilatorul poate fi ajutat cu restrict sau clauza independent
• Compilatorul trebuie să poată determina dimensiuena datelor
utilizate în procesare.
• Se pot folosi directive care controlează explicit dimensiunea
• Aritmetica pointerilor trebuie evitată
• Folosiți accesarea cu indecși în loc de cea cu pointeri.
• Funcțiile apelate în cadrul secțiunii accelerate trebuie sa fie inline.
Algoritmi paraleli si
distribuiți
9. Algoritmi pe grafuri (I): BFS, SSSP cu ∆−𝑠𝑡𝑒𝑝𝑝𝑖𝑛𝑔
+OpenCL
Aplicații
• Determinarea drumului cel mai scurt într-o hartă
• De la 15 secunde (implementare naivă) la 10 microsecunde
Bast, H., Funke, S., Sanders, P., & Schultes, D. (2007). Fast routing in road networks
with transit nodes. Science, 316(5824), 566-566.
• Internet-ul si WWW
• WWW poate fi reprezentat ca un graf direcționat
• Căutarea și parcurgerea (en., crawl): traversarea grafurilor
• Analiza hiperlegăturilor (en., links) și ordonarea (en., ranking): algoritmii page rank si HITS
• Clasificarea paginilor/documentelor și clustering
• Topologia Internet-ului (i.e., rețeaua de routere) are natural structura unui graf.
• Aplicațiile software
• Dependințele de bibliotecă sunt de obicei un graf direcționat aciclic: sortare
topologică
Aplicații (cont.)
• Calcul științific
• Structuri de date pentru exploatarea eficientă a sparsității
• Colorarea grafurilor, arbori de acoperire
• Mulțimi independente, matchings, tehnici de embedding a grafurilor
• Analiza datelor mari (en., large scale)
• Seturi de date complexe, simulari pe date mari (petascale), rețele de senzori
• Provocări: dimensiunea datelor, eterogenitatea, incertitudinea, valorile lipsă,
calitatea datelor
• Astrofizică, bioinformatică, analiza datelor din rețelele sociale
Aplicații (cont.)
• Bioinformatică
• Studierea interacțiunilor care apar între componentele care alcătuiesc un
sistem biologic
• Modelarea: predicția interacțiunilor noi
• Matching/clustering: Anotarea funcțională a unor noi proteine
• Căi/clstering: Identificarea căilor de metabolizare
• Clustering/centralitate: identificarea unor noi complexe de proteine
• Analiza datelor din rețelele sociale
• Publicitatea tintită (en., targeted advertising): clustering și centralitate
• Studierea modului în care se propagă informația
Cercetare care implică algoritmi paraleli pe
grafuri
Aplicații Metode / Probleme Algoritmi pe grafuri Arhitecturi
Analiza rețelelor Determinarea entităților centrale Parcurgeri GPUs
sociale Detectarea comunităților FPGAs
Dinamica rețelei
WWW Marketing Drumuni minime Servere X86
Căutări Conectivitate multicore

Bioinformatică Genetică/genomică Flux maxim Arhitecturi masiv


Metabolism multithread
Calcul științific Partiționarea grafurilor ... Clustere multicore
Colorarea
Cuplaj
Inginerie VLSI CAD ... Clouds
Planificarea căilor
Workflow
• Input: Graful (i.e., lista de adicacență, matrice de adiacență)
• Problema: Determinați căile/clusterele/partițiile/șabloanele/etc.
• Kernel-ul de graf: parcurgere/drum minim/flux maxim/arbore de
acoperire/sortare topologică/etc.
• Factori care infliențează alegerea algoritmului
• Sparsitatea grafului (raportul m/n)
• Natura grafului: static/dinamic, ponderat/neponderat, distribuția gradelor
nodurilor, direcționat/nedirecționat
• Dimensiunea problemei
• Granularitatea calculului necesar la nivel de nod/muchie
• Caracteristici specifice domeniului
Milestones
• 1735: Problema podurilor din Königsberg
https://ro.wikipedia.org/wiki/Problema_podurilor_din_K%C3%B6nigsberg
• 1969: Harary, Graph Theory
https://apps.dtic.mil/dtic/tr/fulltext/u2/705364.pdf
• 1972: Tarjan, DFS and linear graph algorithms
• 1975: Reghbati și Corneil, Parallel connected components
• 1982: Misra și Chandy, Distributed graph algorithms
• 1984: Quinn și Deo, Lucrare studiu (en., survey) asupra ”parallel graph
algorithms”
http://akademik.ube.ege.edu.tr/~erciyes/UBI602/p319-quinn.pdf
Reprezentarea grafurilor
• Grafuri statice (m si n sunt fixe)
• Grafuri dense (m ~= n2): matrice de adiacență
• Grafuri rare (en., sparse): listă de adiacență
• Grafuri dinamice
• Reprezentarea depinde de interogarile cele mai frecvente
• Inderăm/ștergem muchii sau noduri?
• Se modifică ponderile muchiilor?
• Cât de des se modifică graful?
• Interogările (en., queries) se referă la conectivitate, căi, flux?
• Principiu de proiectare: optimizarea locală.
Reprezentarea distribuită a grafurilor
• Replicare completă: fiecare procesor deține întreg graful.
• Partiționarea 1D: Fiecare procesor deține n/p noduri și toate
muchiile adiacente acestor noduri.
• Cum putem să creăm p partiții de noduri?
• Algoritmi de partiționare a grafurilor: Criteriul este optimizarea
”conductanței” (numarul de muchii care traversează tăietura / dimensiunea
partițiilor)
• Amestecarea aleatoare a indecșilor nodurilor asigură că
”<numarul de muchii> /<processor>” este aproximativ același.
Reprezentarea distribuită a grafurilor
• Partiționarea 2D: Se consideră un grid 2D logic de procesoare pentru
o reprezentare cu matrice de adiacență a grafului.
• Atribuim fiecărui procesor o sub-matrice (mai exact, muchiile din
sub-matrice).
• Structuri de date necesare in algoritmii (paraleli) pe grafuri: array, list,
queue, stack, set, multiset, tree.
• Implementările care au constrângeri de perormanță folosesc de cele mai
multe ori array-uri.
Localizarea (en., locality)
necesar mare de memorie + localizare slabă => degradarea
performaței
Ex. Problema centralitatii
2.67 Ghz Intel Xeon 5560
(12 GB RAM, 8 MB L3 cache))
Input: Graf sintetic (m=8n)
LLC = Last Level Cache

https://images.app.goo.gl/PDH5db3RZkcErC6k9
Scalarea algoritmilor
• Algoritmii clasici pe grafuri nu au imbunătățiri ale performanțelor pe
sistemele paralele actuale
• Topologiile grafurilor folosite de algorimii clasici nu corespund seturilor de
date reale.
• Strategiile actuale de paralelizare se bazează pe îmbunătățirea localizării
• Algoritmii clasici nu folosesc noile arhitecturi de calcul paralel: ierarhizarea
memoriei, eterogenitatea procesoarelor, etc.
• Adaptarea implementării pentru a minimiza costurile suplimentare de
paralelizare este dificilă
• Memoria partajată: minimizarea costurilor suplimentare ale lacătelor și barierelor.
• Memorie distribuită: limitarea dimensiunii mesajelor, gruparea convenabilă a mesajelor,
suprapunerea/întrețeserea comunicării cu efectuarea calculelor.
BFS paralel
• Strategia 1. Frontiera curentă se extinde sincron pentru câte un nivel
la un moment dat
• Abordarea este potrivită
pentru grafurile cu diametru mic.
• Necesită O(D) pași, unde D
este diametrul.
• Nodurile vecine frontierei
sunt vizitate în paralel.
BFS paralel
• Strategia 1. Îmbunățirea localizării se poate face prin reetichetarea
nodurilor
• Nodurile inițial nu au etichete, au doar proprietăți: nivel, grad, etc.
• Lipsa etichetei face problema dificilă (i.e., similar învățării nesupervizate din
machine learning).
• Reetichetarea se poate face prin:
• Clustering, daca stim numarul de clustere
• Invațare semisupervizată (topic avansat)
• Daca gradul nodului este mic atunci adiacențele sunt administrate/stocate
explicit într-un index.
• Nodurile cu grad mare sunt procesate în ordine dar si prin blocarea în cache
• Scopul este de a forma blocuri dense în jurul nodurilor cu grad mare.
BFS paralel
• Strategia 1. Optimizări. Scopul este să valorificăm cache-ul partajat și
să diminuăm lipsa informațiilor necesare din cache-ul privat sau TLB
(en., translation lookaside buffer)
http://www.cs.cmu.edu/afs/cs.cmu.edu/user/tcm/www/thesis/subsubsection2
_10_1_2_1.html
• Sortăm listele de adiacență ale fiecărui vector, astfel accesele la memorie vor
fi ordonate și se va fi diminuată lipsa informațiilor din TLB.
• Permutăm etichetele nodurilor pentru îmbunătățirea localizării spațiale
• Blocăm in cache (en., cache blocking) muchiile vizitate recent pentru a
exploata localizarea temporală.
BFS paralel
• Îmbunătățirea localizării prin blocarea cache-ului
BFS paralel
• Strategia 2. Îmbină mai multe traversări concurente1
• Abordarea este potrivită pentru grafuri cu diametru mare
• Parcurgerea muchiilor/drumurilor este gestionată/limitată de *super-noduri*
• Abordarea este asemanatoare cu APSP între *super-noduri*
1
. Ullmann, https://apps.dtic.mil/sti/pdfs/ADA250894.pdf
BFS: Optimizări specifice arhitecturii HW
• *Software prefetching* pe procesoarele Intel Core i7
• Încărcarea speculativă a indecșilor și a nodurilor vecine frontierei in cache va
reduce cantitatea de date lipsă (en., cache miss) necesară procesării.
• Alinierea listelor de adiacență (i.e., localizarea spațială) optimizează accesul la
memorie prin reducerea fragmentării.
• *Hugepage support* contribuie la reducerea semnificativă a lipsei
infomatiilor necesare din TLB
https://en.wikipedia.org/wiki/Page_(computer_memory)
• Alocarea în memoria NUMA-aware este avantajoasă pentru politica
*first-touch*
https://queue.acm.org/detail.cfm?id=2513149
Algoritmi SSSP (en., single source shortest
paths)

• Context SSSP (en., Single Source Shortest Paths, Dijkstra)
• G (n, m) este un graf direcționat cu n noduri si m muchii
• s este nodul sursă
• c este o funcție care atribuie ponderi nenegative reale tuturor muchiilor din G.
• Scop: determinarea costurilor minime ale drumurilor de la s la toate nodurile v în
care se poate ajunge din s.
• Soluție secventială: Algoritmul lui Dijkstra, O(n logn +m) cu movile.
• Bellman-Ford: permite muchii de cost negativ, consideră nodurile în paralel, dar este ineficient
(O(nm)) față de Dijkstra.
• Abordare paralelă: Algoritmul ∆−𝑠𝑡𝑒𝑝𝑝𝑖𝑛𝑔
• Meyer, U., & Sanders, P. (1998, August). δ-stepping: A parallel single source shortest
path algorithm. In European symposium on algorithms (pp. 393-404). Springer, Berlin,
Heidelberg. https://www.cs.utexas.edu/~pingali/CS395T/2012sp/papers/delta-stepping.pdf
• Meyer, U., & Sanders, P. (2003). Δ-stepping: a parallelizable shortest path
algorithm. Journal of Algorithms, 49(1), 114-152.
• https://www.sciencedirect.com/science/article/pii/S0196677403000762
• Notații și constrângeri
• d = gradul maxim din graf
• L = costul celui mai lung drum din graf
• Costul unui drum este egal cu suma costurilor muchiilor care fac parte din acel drum
• Lungimea unui drum este egala cu numarul de muchii parcurse
• d/n probabilitatea ca între două noduri să avem muchie (en., edge probability)
• Modelul G(n, p), unde p=d/n
• Pentru d<1, fiecare componentă conexă este mică
• Pentru d>1, există o componentă conexă mare care conține un anumit procent din noduri. Cu
cât d este mai mare cu atât componenta are mai multe noduri.
• Ex. Nodes = persoane, edge = relatia de prietenie
p = d/n este probabilitatea ca două persoane să se întâlnească și să devină prietene.
Această valoare este statictic independentă de toate celelalte relații de prietenie
existente.
d = numărul de prieteni pe care poate să-l aibă cineva.
Cunoscând valorile lui d și n, cât de mari sunt componentele conexe?


• Ștergerea și relaxarea unui nod dintr-un bucket se poate realiza în
paralel și în orice ordine atâta timp cât relaxarea este implementată
ca o operație atomică (i.e., relaxările pentru același sunt efectuate
secvențial).
• Ca abordare alternativă, toate relaxările unui nod se pot combina într-o
singură relaxare a nodului cu cea mai mică distanță.






• Parelalismul se obține prin scoaterea concurentă a tuturor nodurilor din
primul buchet care nu este gol (i.e., bucket-ul curent) și relaxarea
muchiilor ușoare într-un singură fază.
• Dacă un nod v este scos din bucket-ul B[i] (costul determinat la acest
moment NU este final!) acesta poate fi analizat într-o fază ulterioară,
atunci cand poate fi reinserat în bucket-ul B[i], iar muchiile ușoare care
ies din v vor fi relaxate încă o dată.
• Muchiile grele rămase (care ies din toate nodurile scoase până acum
din B[i]) sunt relaxate o singură dată atunci când B[i] se golește.
• Scopul algoritmului este să caute bucket-uri care mai au muchii ușoare
și să le relaxeze în paralel. La final, toate muchiile grele sunt relaxate
într-o singură fază.


Performanța depinde de alegerea valorii lui ∆ în funcție de
caracteristicile grafului.
Analiza complexității [varianta secvențială]
• Pentru ponderi aleatoare uniform distribuite în [0,1] costurile
suplimentare pentru re-inserări și re-relaxări sunt maxim O(n+m)
pentru △=Θ(1/𝑑) .
• Dacă L este costul maxim al celui mai scurt drum atunci timpul
mediu devine O(n + m + d*L)
• Pentru d*L = O(n+m) timpul mediu este liniar in raport cu n si m, adică
O(n+m).
Obs: Acesta este cazul grafurilor direcționate cu grad cunoscut și limitat.

Exemplu de rulare intuitiv:
https://cs.iupui.edu/~fgsong/LearnHPC/sssp/deltaStep.html

Neo4j Graph Data Science library


Această bibliotecă oferă implementări eficiente pentru versiunile paralele
ale celor mai întâlniți algoritmi pe grafuri pentru Neo4j.
https://neo4j.com/docs/graph-data-science/current/alpha-algorithms/single-source-shortest-pat
h/
” We implement a delta-stepping algorithm that has been shown to outperform Dijkstra’s.”
Kranjčević, M., Palossi, D. and Pintarelli, S., 2016. Parallel delta-stepping
algorithm for shared memory architectures. arXiv preprint arXiv:1604.02113.
https://arxiv.org/pdf/1604.02113v1.pdf
Referințe
• Madduri, K., Bader, D. A., Berry, J. W., & Crobak, J. R. (2007, January).
An experimental study of a parallel shortest path algorithm for
solving large-scale graph instances. In 2007 Proceedings of the Ninth
Workshop on Algorithm Engineering and Experiments (ALENEX) (pp.
23-35). Society for Industrial and Applied Mathematics.
• Meyer, Ulrich, and Peter Sanders. "Δ-stepping: a parallelizable
shortest path algorithm." Journal of Algorithms 49, no. 1 (2003):
114-152.
• https://graph500.org/?page_id=12#sec-7
OpenCL (Open Computing Language)
• https://developer.nvidia.com/opencl
• Este un API de nivel jos pentru calcul eterogen care rulează pe GPU cu
CUDA.
• Dezvoltatorii pot crea și rula kernel-uri folosind limbajul C pe GPU.
• Este o marcă înregistrată a Apple Inc. folosită sub licență de Khronos.
https://streamhpc.com/blog/2017-05-04/what-is-khronos-as-of-today/
• Apple a făcut propunerea inițială și este editor al specificațiilor
Standard pentru programarea pe platforme
eterogene (CPUs, GPUs, etc.)
Scopuri ale OpenCL
• Să permită utilzarea mai multor resurse de calcul dintr-un sistem
• CPUs, GPUs, alte procesoare/acceleratoare
Folosind OpenCL programatorii scriu un singur progrtam care este portabil si
care folosește TOATE resursele dintr-o platformă eterogenă.
• Model de calcul paralel eficient
• Kernele dezvoltate în limbajul ANSI C99
• Abstractizare de nivel jos
• Obținerea de performanță pentru o gamă largă de dispozitive
• Interoperabilitate cu API-urile grafice
• Suport pentru OpenGL, OpenGL ES și DirectX
Modelul platformei OpenCL
• O gazdă este conectată la unul sau mai multe dispozitive (i.e., CPU,
GPU, sau alte procesoare/acceleratoare)
• Fiecare dispozitiv este alcătuit din una sau mai multe unități de calcul
(i.e., un core, multi-procesor, etc.)
• Fiecare unitate de calcul este împărțită în elemente de procesare,
acestea execută codul ca SIMD.
Anatomia unei aplicații OpenCL

Gazda trimite comenzi către dispozitive: 1) pentru a transfera date între memoria
gazdei li memoria dispozitivelor si 2) pentru a executa cod pe dispozitiv(e).
Anatomia unei aplicații OpenCL (cont.)
• Codul serial este executat pe un singur fir al sistemului gazdă (CPU)
• Codul paralel este executat pe mai multe fire pe dispozitive (GPU)
Modelul de execuție OpenCL:
- Aplicațiile rulează pe sistemul gazdă care trimite cereri către dispozitive în
vederea execuției de sarcini (en., work).
- Sarcina (en., work item) = ceea ce trebuie executat pe dispozitiv
- Kernel = codul C care trebuie executat în cadrul sarcinii.
- Program = colecție de kernele (si eventual alte functii) administrare de sistemul
gazdă
- Context = mediul în care se execută sarcinile: dispozitivele cu memoria lor și coada
de comenzi.
- Coada de comenzi = O coadă folosită de aplicația gazdă pentru a trimite sarcini unui
dispozitiv.
Execuția kernel-urilor
• O sarcină (en., work-item) este executată de un element de calcul
(en., compute element)
• Fiecare grup de sarcini (en., work-group) este executat pe o unitate
de calcul (en., compute unit).
• Mai multe grupuri de sarcini concurente se pot găsi pe o unitate de calcul în
funcție de necesarul de memorie și de resursele unității de calcul.
• Fiecare kernel este executat pe un dispozitiv (en., compute device)
Beneficiile utilizării grupurilor de sarcini (en.,
work-group)
• Scalabilitatea automată peste dispozitive cu numar diferit de unități
de calcul
• Grupurile de sarcini se pot executa în orice ordine, concurent sau secvențial.
• Cooperare eficientă între sarcininile din cadrul aceluiași grup de
sarcini
• Memorie partajată rapidă și sincronizată
• Independența dintre grupurile de sarcini oferă scalabilitate
• Un kernel se poate scala/rula pe orice număr de unități de calcul.
Memoria în OpenCL
• Spațiul de adrese este:
• Privat: Acces R/W numai la nivel de sarcina (i.e., work-item).
• Local: Acces R/W numai la nivel de grup de sarcini (en., work-group) .
• Global/constant: vizibil către toate grupurile de sarcini.
• Al gazdei: accesibil de CPU.

• Sincronizarea
• Toate sincronizările necesare pentru acces la memorie trebuie realizate
explicit.
Obs: Administrarea memoriei se face explicit: Datele trebuie mutate pe ruta
gazdă->global->local … și înapoi.
Dezvoltarea de aplicații cu OpenCL
• Limbajul OpenCL si API necesar
• API la nivel de platformă (apelabil din sistemul gazdă)
• Abstractizarea accesului la resursele de calcul
• Interogarea, selectarea si inițializarea dispozitivelor
• Crearea de contexte de calcu și a cozilor de lucru
• API la rulare (apelat de gazdă)
• Executa kernel-uri
• Stabilirea configurației de execuție a kernel-ului
• Administrarea planificarilor, calculelor și a resurselor de memorie
• Limbajul OpenCL
• Kernel-urile sunt scrise în C
• Sit disponibile multe funcții
• Compilarea se poate face JIT/online sau offline
Limbajul OpenCL
• Calificarea funcțiilor
• Calificatorul __kernel declara o funcție ca și kernel.
• Calificarea spatiului de momorie
• __global, __local, __constant, __private
• Funcții pentru lucrul cu sarcinile
• get_work_dim(), get_global_id(), get_local_size()
• Funcții pentru lucrul cu imaginile
• Imaginile pot fi accesate prin funcții built-in
• Funcții pentru sincronizare
• Barierele: toate sarcinile dintr-un grup trebuie să execute funcția barieră înainte ca
orice sarcină din grup să poată fi executată.
Tipuri de obiecte în OpenCL
• cl_platform_id: identificator pentru o anumită platformă
• cl_device_id: identificator pentru un anumit dispozitiv
• cl_context: handle pentru un context de calcul
• cl_command_queue: handle pentru o coadă de comenzi (a unui dispozitiv)
• cl_mem: handle pentru o resursă de memorie (dintr-un context)
• cl_program: handle pentru un program (într-o bibliotecă de kernel-e)
• cl_kernel: handle pentru un kernel.
• Toate obiectele au numarul de referințe numărat, iar sistemul are propriul
garbage collector
• Când numărul de referințe ajunge la 0 atunci memoria obiectului este eliberată.
OpenCL C
• Derivat din ISO C99 Limitări:
- Nu sunt permise apelurile recursive
• Proprietăși adăugate limbajului - Nu sunt permiși pointerii la funcții
• Existenșa sarcinilor si a grupurilor - Sunt permisi pointerii la pointeri dar
de sarcini intr-un kernel, nu ca parametri.
• Tipul vector - Nu sunt permise array-uri sau
• Sincronizare structuri de dimensiune variabilă.
• Functii build-in pentru - Etc..
• Lucrul cu imagini
• Lucrul cu sarcini
• Funcții matematice
Algoritmi paraleli si
distribuiți
10. Algoritmi pe grafuri (II): Prim, Kruskal, APSP (n x Dijkstra, Roy-Floyd),
componente conexe, SSSP pe grafuri rare.
+ Neo4j
Arbore parţial de cost minim (APM)
(en., MST = Minimum cost spanning tree)
• Arborele de acoperire pentru un graf nedirecționat G(V, E) este un
subgraf al lui G care este un arbore care conține toate nodurile din G.
• Subgraf = submultime de muchii din E
• Arbore = graf (nedirecționat) fără cicluri
• De acoperire = conține toate nodurile din G
• APM pentru un graf ponderat este arborele de acoperire cu cost
minim.
• Costul = suma costurilor muchiilor din APM
• Algoritmi care rezolvă aceasta problemă
• Prim, Kruskal, Boruvka
Algoritmul lui Prim pentru APM
• Este greedy și iterativ
• Alege un nod de start oarecare
• Alege un nou nod (ca Dijkstra) care este inclus in APM
• Timp O(n2)

Scopul paralelizării:
- Obținerea accelerării în urma rulării pe mai multe procesoare
- Procesarea de grafuri mari care nu pot fi rezolvate de un singur
procesor
Algoritmul lui Prim: formulare paralelă

Algoritmul lui Prim: formulare paralelă
(cont.)

Algoritmul lui Prim: formulare paralelă
(cont.) Contextul problemei
- Reprezentarea grafului: matrice de adiacență
Algoritm paralel referință - Graful este distribuit catre procesoare
- Nodul 0 este adăugat în APM (i.e., soluție) - V este împărțită în submulțimi
- Fiecare procesor se ocupă de o submulțime
- Fiecare procesor pi de noduri si toate muchiile acestora.
- Determină cel mai apropiat nod de APM-ul curent
- La primul pas APM-ul este alcătuit doar din nodul 0
- Transmite muchia de cost minim către procesul părinte
- Reducerea MIN (executată in procesul părinte)
- Procesul părinte determină minimul global. Acest pas are ca rezultat
determinarea/cucerirea uni nod care este adaugat la APM.
- Nodul nou adăugat/cucerit este transmis (en. , broadcast) către toate prosoarele.
- Procesoarele fac update in partea de graf gestionată.
- Acești pași se repetă până când APM-ul contine toate nodurile din V.
Algoritmul lui Prim: formulare paralelă
(cont.)
• Limitări ale variantei paralele de referință
• Pe grafurile mici (<10K de noduri) accelerarea este minimală
• Pe grafuri mari (25K-100K de noduri) costurile suplimetare (en., overhead)
generate de comunicare domină timpul necesar procesărilor ceea ce conduce
la lipsa accelerării.
• Posibile îmbunătățiri ale performanței
• Utilizarea mobilelor binome sau Fibonacci pentru determinarea minimului
• Utilizarea listei de adiacență pentru reprezentarea grafului
Algoritmul lui Kruskal
• Algoritmul lui Kruskal construiește o multime de APM în paralel.
• Inițial, fiecare nod este un arbore separat.
• Se sortează muchiile în ordine crescătoare a costurilor.
• Muchiile sunt analizate in ordinea costurilor
• Dacă muchia conectează doi arbori distincți atunci acestia sunt uniti
• Dacă muchia uneste două noduri ale aceluiași arbore atunci ea face un ciclu și deci
este ignorată.
• Algoritmul se termină când
• Nu mai avem muchii
• Avem un singur arbore, care este APM-ul căutat.
• Obs: Implementarea clasică este cu Union-Find.
Algoritmul lui Kruskal: formulare paralelă
• Varianta 1. Prin paralelizarea sortării
Având in vedere că algoritmul are două faze (i.e., Sortare si Parcurgere)
o primă opțiune este paralelizarea operației de sortare urmată de
parcurgere în mod asemănător ca în varianta secvențială.
Algoritmul lui Kruskal: formulare paralelă
(cont.)

Algoritmul lui Kruskal: formulare paralelă
(cont.)

Algoritmul lui Dijkstra: formulare paralelă
• Utilizează aceeași abordare ca algoritmul lui Prim pentru APM.
• Pas 1. Matricea de adiacență este partiționată pe coloane (i.e., subgraf cu o
submulțime de noduri si tote muchiile acestora)
• Pas 2. Fiecare proces selectează local nodul cel mai apropiat de nodul sursă.
Procesul părinte analizează toate soluțiile/propunerile locale și decide nodul
cucerit.
• Pas 3. Nodul cucerit este transmis (i.e., broadcast) către toate procesoarele
pentru ca vectorul de distanșe să fie actualizat.
• Ca și în algoritmul lui Dijkstra secvențial, algoritmul se termină atunci
când toate nodurile au fost cucerite (i.e., li s-a calculat costul minim)
• Presupunem că graful constă din noduri la care se poate ajunge din nodul
sursă.
Algoritmul lui Dijkstra pentru APSP
(en., All-Pairs Shortest Paths)
• Variantă secvențială
• Rezolvăm problema APSP prin n execuții ale algoritmului lui Dijkstra, cate una
din fiecare nod al grafului.
• Limitări:
• Algoritmul nu funcționează corect pentru costuri negative.
• Complexitatea este O(n3log n ) pentru grafuri dense.
• Pentru costuri negative (i.e., trebuie Bellman-Ford) complexitatea devine O(n4)
• Variantă paralelă
• Se partiționează sursele: fiecare apel de SSSP (Dijkstra/Bellman-Ford) se
execută pe câte un procesor separat.
• Se paralelizează SSSP pentru cresterea concurenței.
APSP cu Dijkstra: paralelizare cu
partiționarea surselor
• Fiecare procesor Pi determină un SSSP de la nodul vi la toate celelalte
noduri prin executarea algoritmului lui Dijkstra secvențial.
+ Nu apar costuri suplimentare cu comunicația, daca graful este replicat pe
toate procesoarele.
- Putem folosi maxim n procesoare.
Tp = O(n2) O(n2) , ca il cazul algoritmului lui Prim.
+ Paralelizarea SSSP face posibilă utilizarea a n2 procesoare.
APSP: Algoritmul Roy-Floyd: varianta paralelă

APSP: Algoritmul Roy-Floyd: varianta paralelă

APSP: Algoritmul Roy-Floyd: varianta paralelă

Transmiterea distanțelor actualizate


APSP: Algoritmul Roy-Floyd: varianta paralelă
Procedure Roy_Floyd_2DBlock(D(0))
for k=1 to n do
# fiecare proces Pi,j care are un segment al rândului k din D(k-1)
- transmite distanțele către procesele P*,j
# fiecare proces Pi,j care are un segment al coloanei k din D(k-1)
- transmite distanțele către procesele Pi,*
# fiecare proces așteaptă să primească segmentele necesare
# fiecare proces Pi,j calculează distanțele din blocul D(k) al matricii.
APSP: Algoritmul Roy-Floyd: varianta paralelă
• Putem actualiza valorile din matrice concurent?
a[i][j] = MIN (a[i][j], a[i][k]+a[k][j] );
• Da, deoarece rândul și coloana k nu se schimbă (i.e., fac o singură
actualizare) în timpul iterației k.
• Din perspectiva metodologiei Foster
• Partiționarea: fiecare a[i][j] este un task
• Comunicarea: în timpul iterației k, actualizarea valorii din a[i][j] necesită
valorile a[i][k] si a[k][j]
• Se transmite (en., broadcast) a[i][k] către a[i][0], a[i][1], ..., a[i][n-1]
• Se transmite a[k][j] către a[0][j], a[1][j], ..., a[n-1][j]
APSP: Algoritmul Roy-Floyd: varianta paralelă
APSP: Algoritmul Roy-Floyd: varianta paralelă
Alglomerarea si alocarea
• Un process este responsabil pentru o parte din matricea a[][]
• Matricea a[][] este convenabil împărțită
• Părțile a[i][j] trebuie să fie de dimensiune egală
• O împărțire convenabilă este pe rânduri
• Unui proces ii vor fi alocate un număr de rânduri condecutive din a[][]
• Această împărțire este convenabilă deoarece valoarea a[i][k] se află la
dispoziția aceluiași proces ca și a[i][j]
• Totuși, a[k][j] se află în memoria altui proces => Este nevoie de comunicare!
• Înainte de iterația k, procesul care detine rândul k il va transmite către toate celelalte
procese.
APSP: Algoritmul Roy-Floyd: varianta paralelă

APSP: Algoritmul Roy-Floyd: varianta paralelă

Componente conexe: abordare paralelă
• Algoritmii secvențiali: DFS, BFS, Kosaraju.
• Abordare paralelă:
• Distribuim graful între procesoarele disponibile
• Fiecare procesor determină componentele conexe pentru subgraful primit
• La acest moment avem p păduri de componente conexe
• Pădurile de componente conexe sunt unite până când rămânem cu o singură
pădure.
• Unirea eficientă a perechilor de componente conexe din păduri diferite
trebuie să utilizeze Union-Find.
• Union(x, y): unește două componente conexe care sunt disjuncte.
• Find(x): returnază un pointer la reprezentantul componentei conexe care conține pe
x. Fiecare componentă conexă are un reprezentat unic și diferit de celelalte
componente din aceeași pădure.
Componente conexe: abordare paralelă
(cont.)
• Unirea pădurilor A și B
• Pentru fiecare muchie (u, v) din A de vor rula operațiile find(u) și find(v)
pentru a determina dacă nodurile u și v sunt într-o componentă (i.e., au
același reprezentatnt) ca un arbore din B.
• Dacă cele două noduri sunt în doi arbori (i.e., componente conexe) diferiți atunci aceștia
sunt uniți prin apelarea operației unite(u, v).
• Unirea a două păduri A și B necesită cel mult 2(n-1) operații find și (n-1)
operații union.
Componente conexe: abordare paralelă
(cont.)

Algoritmi pe grafuri rare
• Un graf G(V, E) este rar dacă m << n2 (numarul de muchii este mult mai mic
decat numarul maxim de muchii).
• n < m < n2 , sau numarul de muchii este putin mai mare decât numarul de noduri.
• Exemple de graduri rare: liniare (i.e., fiecare nod are doar doi vecini), grid (fiecare
nod are 4 vecini) sau graf rar aleator.
• Dacă graful este rar atunci eficienta algoritmilor se poate îmbunătății
• APM cu Prim: de la O(n2) la O(m log n).
• Pentru grafurile rare se preferă reprezentarea cu liste de adicacență.
• Partiționarea unei liste de adicacență poate fi dificilă
• Realizăm balansarea în funcție de numărul de noduri?
• Putem utiliza informatiile referitoare la gradul nodurilor?
Algoritmi pe grafuri rare: Exemplu
• Reprezentarea unei hărți a străzilor cu ajutorul grafurilor
Algoritmul lui Johnson: SSSP in grafuri rare
• Este echivalentul algoritmului lui Dijkstra pentru grafuri rare
• Algoritmul lui Dijkstra analizează numai nodurile adiacente celor cucerite.
• Algoritmul lui Johnson folosește o coadă de priorități pentru a gestiona
valoarea l[v] pentru fiecare nod din V-VT
• Observație: Menținerea unei ordin stricte conduce la restrangerea
oportunităților de paralelizare.
• Soluție: Permiterea explorării mai multor noduri în paralel.
• Se extrag simultan p noduri din coada de priorități
• Se actualizează costurile vecinilor cu costurile minime.
• În situația unei erori aceasta va fi descoperită ca o cale mai scurtă și nodul se va
reintroduce cu costul minim actualizat.
Algoritmul lui Johnson: SSSP in grafuri rare
(cont.)
• Dacă extragem și procesăm mai multe noduri din coadă atunci aceasta va determina un
blocaj (en., bottleneck)
Soluția:
• Utilizarea mai multor cozi, câte una pentru fiecrae procesor.
• Fiecare procesor își construiește propria sa coadă numai pentru nodurile pe care le gestionează.
• Atunci când procesul Pi extrage nodul u din Vi el trimite un mesaj proceselor care
gestionează noduri adiacente cu u.
• Atunci când Pj primește mesaj el actualizează valoarea lui l[v] în coada lui la valoarea
min{l[v],l[u] + w(u,v)}
• Dacă un drum mai scurt este obținut către v de Pj atunci acesta este reactualizat în coada
locală lui Pi.
• Algoritmul se termină atunci când toate cozile sunt goale.
Obs: Mai multe opțiuni de partiționare pot fi folosite pentru a exploata structura grafului
care trebuie analizat.
Frameworks/biblioteci pentru procesarea
grafurilor
Software Limbaj/ paralelizare
interfață
SNAP (https://snap.stanford.edu/snap/ ) C Partajata (openMP)
Parallel Boost Graph Library C++ În memorie
(https://www.boost.org/doc/libs/1_63_0/libs/graph_parall
el/doc/html/index.html )
Knowledge Discovery Toolbox (KDT) Python În memorie
http://kdt.sourceforge.net/wiki/index.php/Main_Page
Pegasus Hadoop Pe disc.
https://www.cs.cmu.edu/~pegasus/index.htm

Scalable R-MAT generator


https://www.boost.org/doc/libs/1_53_0/libs/graph_parallel/doc/html/scalable_rmat_generator.html
SNAP R-MAT generator
https://snap.stanford.edu/snappy/doc/reference/GenRMat.html
Baze de date graf
Ce sunt bazele de date graf?
• Muchie = relație între instanțe/înregistrări
• Nod = instanță/înregistrare
Caracteristici:
• Mai putin rigid structurate ca bazele de date relaționale (sql)
• Un nod (instanță/înregistare) poate foarte multe muchii (i.e., grad mare)
• O muchie poate avea mai multe caracteristici
• Nu există nici o limitare în ceea ce privește gradul unui nod sau numarul de caracteristici
• Nodurile sunt definite de caracteristici, numite proprietăți (i.e., nume, ID,
etc.)
• Nodurile se pot conecta prin muchii direcționate sau nedirecționate
Baze de date graf (cont.)
• Prin proiectare, bazele de date graf pot fi utilizate pentru reprezentarea
rețelelor sociale
• Nod = persoana, cu caracteristicile nume, vârstă, etc..
• Muchie = relatia prieten, cu caracteristici de tipul pondere sau tip (i.e., coleg de
servici, rudă, etc.)
• Aplicații utile
• Structura grafului, sisteme de recomandare, etc.
• Caracteristici
• Creare și întreținere, ca la orice baza de date.
• Operații CRUD (i.e., create, read, update, and delete)
• Interogări simplificate comparativ cu cele relationale
https://www.g2.com/categories/graph-databases
Neo4j (https://neo4j.com/ )
• Conexiunile/legăturile dintre inregistrări sunt cel puțin la fel de
importante ca și datele
• Google: paginile sunt nodurile, hiperlink-urile sunt muchii
• Facebook/Linkedin: utilizatorii sunt nodurile, relatiile sunt muchiile
• Amazon: utilizatorii/produsele sunt noduri, tranzacțiile sunt muchiile
• Limitele BD relaționale
• O interogare poate lua in calcul multe date=> interogarile în timp real trebuie
să aibă timp de răspuns mic
• Interogarile pot analiza datele in mod nesecvențial, pe o cale aleatoare în
date.
Neo4j (cont.)
• Neo4j
• Gestionează și interoghează date care au structură de graf
• Asigura parcurgere în timp real
• Permite adăugarea facilă de date
• Interogarile nu folosesc fisiere de index, și permit 1M+ hops/second cu
ajutorul pointerilor
• Platforme
• Linux, Solaris, Windows, macOS
• Cloud: Amazon web services (AWS), Microsoft Azure, docker, etc.
Baze de date relaționale vs. Baze de date graf

Folosim RDBMS atunci când avem: Folosim BD graf atunci când avem:
• Structură bine cunoscută și • Structură dinamică în care topologia
detrminată care nu se schimbă datelor este greu de evaluat.
frecvent • Relațiile dintre date (instanțe) au
înțeles și valoare
De la RDBMS la BD Graf
(dezvoltare/administrare)
Componentele unei baze de date graf
• Nodurile
• Sunt obiecte în graf
• Pot fi descrise de proprietăți de tipul nume-valoare
• Pot avea etichete

• Relațiile
• Leagă nodurile în funcție de tip și direcție
• Pot fi descrise de proprietăți de tipul nume-valoare
Avantaje ale utilizării BD graf
Modelul relațional vs. Modelul graf

• Intuitivitate
• Viteză
• Interogările sql din
bazele de date
relationale devin
cypher. Fără indexare!
Cypher (i.e., sql in bazele de date relationale)
• Crearea datelor (i.e, noduri și muchii)
CREATE(:Person{name:”Ann”}) – [:LOVES]-> (:Person{name:”Ann”})
• Noduri :Person{name:”Ann”}, where Person is label and name:”Ann” este o
proprietate.
• LOVES: este eticheta relației (i.e., atributul muchiei care leagă )
• Reprezentarea/crearea muchiilor bidirecționate/nedirecționate
MATCH(:Person{name:”Ann”}) – [:FB_FRIENDS]-> (:Person{name:”Dan”})
MATCH(:Person{name:”Ann”}) – [:FB_FRIENDS]- (:Person{name:”Dan”})
Cypher (cont)
• Cum ar arăta interogarea următoare cypher in sql?
http://www.opencypher.org/
• GQL (graph query language) este omologul sql pentru BD graf.
• Utilizarea urmează aceiași pași ca la BD relaționale
• Se crează modelul
• Se populează cu date
• Se interoghează
• Limbaje de programare
• .Net, JS, Java, Python, rubby, php
• Jetbrains IDE (https://www.jetbrains.com/products/ ) vine cu Plugin pentru
Graph Database Developers
https://neo4j.com/blog/jetbrains-ide-plugin-graph-database/
• Arhitectural
• MVC cu inlocuirea BD (relationale, NoSQL, Hadoop) cu BD graf.
Algoritmi paraleli in Neo4j
PageRank
https://neo4j.com/docs/graph-data-science/current/algorithms/page-rank/
SSSP cu ∆−𝑠𝑡𝑒𝑝𝑝𝑖𝑛𝑔
https://neo4j.com/docs/graph-data-science/current/alpha-algorithms/single-source-shorte
st-path/
APM
https://neo4j.com/docs/graph-data-science/current/alpha-algorithms/minim
um-weight-spanning-tree/
Machine Learning
https://neo4j.com/docs/graph-data-science/current/algorithms/node2vec/
https://neo4j.com/docs/graph-data-science/current/algorithms/ml-models/
Referințe:
Lončar, V., Škrbić, S., & Balaž, A. (2014). Parallelization of minimum
spanning tree algorithms using distributed memory architectures. In
Transactions on Engineering Technologies (pp. 543-554). Springer,
Dordrecht. http://www.scl.rs/papers/Loncar-TET-Springer.pdf

Katsigiannis, A., Anastopoulos, N., Nikas, K., & Koziris, N. (2012, May).
An approach to parallelize Kruskal's algorithm using helper threads. In
2012 IEEE 26th International Parallel and Distributed Processing
Symposium Workshops & PhD Forum (pp. 1601-1610). IEEE.
http://www.cslab.ntua.gr/~anastop/files/papers/mtaap12kruskal.pdf
Algoritmi paraleli si
distribuiți
11. Algoritmi distribuiți: Alegerea liderului, Map-Reduce
+ Hadoop
Alegerea liderului
• Context
• Avem un sitem distribuit, de exemplu procese într-o rețea
• Scop
• Procesele trebuie să aleagă un lider, adică un proces *principal*
• Procesul lider ales:
• Va fi organizatorul unui task distribuit, de exemplu
• Rădăcina unui APM pentru rețea
• Inițiatorul unui algoritm de reconstruire a token-ului pierdut intr-o rețea inel.
• Constrângeri referitoare la procesul de alegere
• Se pornește de la situația în care procesele nu au nici o intuiție referitoare la
procesul care va deveni lider
• Siguranță (en., safety): Doar un singur proces este ales ca lider
• Liveness: în cel mai rău caz trebuie ales un lider
Alegerea liderului (cont.)
• Algoritmul de alegere trebuie să fie descentralizat
• Inițiatorii pot fi mulțimi nevide de procese
• Toate procesele trebuie să execute același algoritm
• Se preîntâmpină astfel situația în care un proces decide unilateral ca ”Eu sunt liderul!”
• Procesele au ID-uri distincte și formază o mulțime total ordonată.
• Unicitatea ID-ului este esențială pentru ca algoritmul de alegere sa te termine
întotdeauna.
• Algoritmii sunt exemplificați in rețele de tip inel orientat
• Procesele stiu ce este in sânga (i.e., in sesul acelor ce ceas) și în dreapta (i.e.,
in sesul invers acelor de ceas.)
Rețea inel
1 = stânga = in sesul acelor ce ceas
2 = dreapta = in sesul invers acelor ce ceas

De exemplu, dacă mesajele sunt transmise pe


canalul 1 atunci ele vor traversa rețeaua inel în
sensul acelor de ceas.
Motivația pentru studiul rețelei inel:
- Este ușor de analizat
- Rezultatele se aplică pentru topologii
oarecare/arbitrare
Alegerea liderului: Definiție
• Fiecare procesor poate avea două stări: ELECTED (ro., ales) sau
NOT-ELECTED (ro., neales).
• În momentul în care procesorul trece în starea ELECTED el va rămâne
pentru totdeauna în starea ELECTED
• La fel se întâmplă și pentru trecerea în starea NOT-ELECTED.
• Obs: Acesta este principiul ireversibilibății deciziei luate.
• În timpul execuției
• Orice procesor poate trece în starea ELECTED sau NOT-ELECTED
• La finalul execuției
• Doau un singur procesor (i.e., liderul) se va afla în starea ELECTED.
Alegerea liderului: Algoritmi
• Algoritmii pentru alegerea liderului trebuie să aibă în vedere
următoarele caracteristici ale rețelei
• Inelul este anonim/ne-anonim
• Anonim = procesoarele nu au id-uri unice!
• N (i.e., dimensiunea rețelei) este o valoare
• Cunoscută: inel/rețea ne-uniform(ă)
• Necunoscută: inel/rețea uniform(ă)
• Algoritmul este sincron/asincron
• Inel sincron anonim
• Fiecrae procesor execută același algoritm
Algoritmi uniformi în rețele anonime
• Algoritmii uniformi nu iau in calcul dimensiunea rețelei (i.e., avem
același algoritm indiferent de dimensiunea rețelei)
• Toate procesoarele - indiferent de dimensiunea rețelei – sunt modelate cu
ajutorul aceleiași mașini de stări.
• Algoritmii ne-uniformi iau în calcul dimensiunea rețelei
• Algoritmi diferiți pentru fiecare dimensiune a rețelei
• Obs: Pentru fiecare valoare N, procesoarele din rețea vor fi modelate cu
aceeași mașină de stări AN
• Obs: Aceste rețele sunt anonime, adică nu au ID-uri unice.
Alegerea liderului în rețelele anonime
Teoremă: Nu există nici un algoritm de alegere a liderului pentru rețelele
anonime, chiar dacă se cunoaște dimensiunea rețelei (i.e., este neuniformă)
și rețeaua este sincronă.
Demonstrație (schiță):
- Toate procesoarele încep dintr-o anumită stare și deci transmit același
mesaj (atenție, rețeaua este anonimă)
- Toate procesoarele primesc același mesaj, efectuează aceeași tranziție și
transmit același mesaj, de fiecare data!
- La un moment dat, dacă un procesor ajunge in starea ELECTED atunci și
toate celelalte procesoare vor ajunge tot in starea ELECTED.
- Astfel, nu sunt îndeplinite criteriile de siguranță și liveness.
- Obs: Rezultatul este valabil și în rețelele uniforme (i.e., cu N cunoscut) și asincrone.
Rețele inel cu identificatori (i.e., neanonime)
• Fiecare procesor are un ID unic.
• NU trebuie confundate pozițiile/indecșii cu ID-urile.
• Pozițiile/indecșii sunt de la 0 la N-1, sunt folosiți doar pentru analiză și nu sunt disponibili
procesoarelor
• ID-urile sunt valori intregi nenegative care sunt cunoscute procesoarelor prin variabila
locală id.
• Definirea unei rețea inel
Parcurgeți rețeaua alăturată de la
cel mai mic id către stânga (ordinea
acelor de ceas)
3, 37, 19, 4, 25
Algoritmul Chang-Roberts: abordare
[rețea inel neanonimă asincronă]
Fiecare proces execută următoarea logică:
• Transmite un mesaj către stânga (în sensul acelor de ceasornic) cu
ID-ul propriu, dacă nu a primit un mesaj cu un ID mai mare.
• Transmite către stânga orice mesaj cu ID mai mare decât ID-ul
propriu.
• Dacă procesul primește ca mesaj ID-ul propriu atunci el este liderul.
• Obs: Numărul de procesoare nu trebuie să fie cunoscut de algoritm.
Algoritmul Chang-Roberts
[pseudocod pentru procesul Pi]
state := cadidat;
send(my_ID);
while (state != leader)
do receive (vecin_ID)
if (vecin_ID > my_ID) then send (vecin_ID);
#end do
if (vecin_ID = my_ID) then state := leader.
#end_while
Algoritmul Chang-Roberts
[exemplu de execuție]
Fiecare nod trimite un mesaj către
stânga.

Mesajul conține ID-ul nodului care


trimite.
Algoritmul Chang-Roberts
[exemplu de execuție] (cont.)
Dacă: ID-ul mesajului primit > ID-ul nodului current
Atunci: Transmite ID-ul primit mai departe
Algoritmul Chang-Roberts
[exemplu de execuție] (cont.)
Dacă: Un nod primește propriul mesaj
Atunci: Nodul devine lider
Algoritmul Chang-Roberts: Analiză
• Corectitudinea:
• Algoritmul alege procesorul cu cel mai mare ID.
• Mesajul care conține cel mai mare ID trece pe la toate procesoarele.
• Complexitatea comunicației depinde de modul în care sunt distribuite
ID-urile pe procesoare
• ID-ul cel mai mare parcurge intreg inelul: sunt transmise n mesaje
• Al 2-lea cel mai mare ID parcurge inelul până când ajunge la procesorul cu ID-ul cel
mai mare, etc...
• Cazul cel mai defavorabil costă O(n2), atunci când procesoarele sunt on ordine
descrescătoare: 1+2+...+n = O(n2)
• Cazul cel mai favorabil costă O(n): n+1+1+...+1 = O(n)
• În medie, costul este O(n logn), în cazul în care toate ID-urile sunt echiprobabile.
Algoritmul lui Franklin: inel nedirecționat asincron
• Scop: Alegerea liderului, adică a nodului cu ID-ul maxim.
Context
• Inelul este bidirecțional
Abordare generală
• Alegerile sunt efectuate in faze (asincron), pe mulțimi din ce in ce mai mari
• Pi este lider în faza r = 0,1, ...,
<==>
Pi are cel mai mare ID dintre toate nodurile care se află la o distanță de maxim 2r
• Faza r necesită cel mult 4*2r pași
Algoritmul lui Franklin (cont.)
• K-vecinătate
• Sunt nodurile care se află la distanța de maxim k de nodul selectat.
• Abordare
• Numai procesele care câștigă alegerile în faza r continuă pentru faza r+1
• Dacă un proces primește înapoi mesaj cu propriul ID atunci acesta devine
LIDER.
• Obs: Algoritmul este uniform, adică numărul de procese nu trebuie cunoscut
dinainte.
Algoritmul lui Franklin: exemplu de execuție
Faza 0: If: ID_primit > ID_curent
send(ID, faza, pasul) către 1-vecinătate Then: Trimite răspuns OK
Algoritmul lui Franklin: exemplu de execuție
(cont.)
If: un nod primește ambele răspunsuri
Then: nodul devine lider temporar
si trecem la faza urmăroare

În urma fazei 0 avem următorii lideri


temporari: 5, 6, 7 și 8.
Algoritmul lui Franklin: exemplu de execuție
(cont)
Faza 1: // send(ID, faza, pasul) If: ID_primit > ID_curent
send(ID, 1, 1) către 2-vecinătate Then: forward (ID, 1, 2)
Algoritmul lui Franklin: exemplu de execuție
(cont)
La pasul 2 am ajus deja la granița If: ID_primit != ID_curent
2-vecinătății Then: forward (ID_primit)
If: ID_primit > ID_curent If: un nod primește ambele răspunsuri
Then: trimite răspuns (ID_primit) Then: nodul devine lider temporar
Algoritmul lui Franklin: exemplu de execuție
(cont) 2
La pasul 2 : If: ID_primit > ID_current
Then: trimite răspuns (ID_primit)
Faza 2: // send(ID, faza, pasul) If: un nod primește ambele răspunsuri
send(ID, 2, 1) către 22-vecinătate Then: nodul devine lider temporar
Algoritmul lui Franklin: exemplu de execuție
(cont)
Faza 3: In general: N noduri => log n faze
i-1
3
send(ID, 3, 1) către 2 -vecinătate În faza i: ID-ul se trimite la 2 –vecinătate
Nodul 8 va primi înapoi propriul
ID și atunci devine lider. Complexitatea comunicației:
- Fiecare mesaj aparține unei faze și este
inițiat de un anumit proces.
- Nr. de mesaje inițiate de un proces în faza i
este cel mult 4*2i (cu trimiteri si
răspunsuri în ambele direcții)
Algoritmul lui Franklin: exemplu de execuție
(cont)
Complexitatea comunicației:
Nr. max. de mesaje / lider Nr. max. de lideri curenți
Faza 1: 4 x n =4n
Faza 2: 8 x n/2 =4n
...
Faza i: 2i+1 x n/2i-1 =4n
...
Faza log n: 2logn +1 x n/2logn-1 =4n
-------

Nr. total de mesaje = O(n logn)


Algoritmul lui Franklin: concluzie
• Algoritmul O(n logn) este mai complicat decât cel O(n2) dar folosește
mai puține mesaje în cazul cel mai defavorabil.
• Funcționează pentru inel sincron si asincron
• Numărul de mesaje nu poate fi redus în rețelele asincrone.
• Pentru rețea sincronă și neuniformă (i.e., n este cunoscut) un
algoritm eficient rezolvă problema în O(n).
Hadoop
Referințe
• Venner, J. (2009). Pro hadoop. Apress.
• White, T. (2012). Hadoop: The definitive guide. " O'Reilly Media, Inc.".
• https://hadoop.apache.org/
Despre Apache Hadoop
• Ce este Apache Hadoop?
• Este un framework software open source (sub licență Apache) proiectat
pentru stocarea și procesarea de date mari pe clustere de hardware comun.
• Cine a creat Hadoop?
• Doug Cutting și Mike Carafella în 2005.
• Când se folosește Hadoop?
• La procesarea de texte mari
• La asamblarea genomului
• La procesarea grafurilor
• În Machine learning
• La analizarea rețelelor sociale mari
Cine folosește Hadoop?
Ecosistemul Hadoop
• Hadoop Common:
• Conține bibliotecile principale și alte module

• HDFS
• Hadoop Distributed File System: *a self-healing high-bandwidth clustered
storage*
• Hadoop MapReduce:
• Un model de programare pentru procesarea de date mari
Folosiți unealta potrivită pentru task-ul avut
Baze de date relaționale Vs Hadoop
Se folosesc atunci când:
- Procesarea interactivă OLAP* (<1sec) - Avem sau nu date structurate
(flexibilitate)
- Avem tranzacții ACID - Scalabilitate a procesărilor și a
dimensiunii datelor procesare
- Interogările sunt 100% SQL conforme - Procesarea datelor complexe
OLAP = Online analytical processing
Stocarea datelor
• Avem două tipuri de noduri: de date și de procesare
• Abordarea:
• La rulare, datele sunt copiate în nodurile de procesare: acest mod de lucru
este viabil numao când lucrăm cu cantități relativ mici de date.

• Ce înseamnă mult?
• Facebook (500 TB pe zi); Yahoo (peste 170 PB); eBay (peste 6 PB)
• Aducerea de date la procesoare devine un bottleneck!
Cerințe pentru Hadoop:
• Trebuie să gestioneze defecțiunile (en., failure) unor componente
astfel încât să nu fie pierdute date
• Rezultatul final nu trebuie sa fie afectat

• Trebuie să fie scalabil:


• Creșterea cantității de date trebuie să aibă ca efect scăderea uniformă a
performanței pentru toate task-urile
• NU întreruperea sistemului
• Creșterea de resurse alocate trebuie să aibă ca efect creșterea capacității de
procesare.
Hadoop
• Dean, J., & Ghemawat, S. (2004). MapReduce: Simplified data
processing on large clusters.
• Abordare:
• Se distribuie datele pe mediul de stocare [în HDFS].
• Fiecare nod poate apoi executa calculele necesare pe datele pe care le deține
fără a fi nevoie de mutarea acestora pentru procesarea inițială.
Concepte de bază
• Aplicațiile sunt dezvoltate într-un limbaj de programare de nivel înalt
(i.e., Java)
• NU este nevoie de programare în rețea sau administrarea dependințelor
temporale sau a concurenței
• Nodurile trebuie să comunice cât mai puțin între ele
• Arhitectura este de tip ”nimic partajat” (en., shared nothing)
• Datele sunt distribuite între sisteme de la bun început (cu
redundanță)
• Se efectuează procesările acolo unde datele sunt deja existente
Fluxul de procesare
• Se încarcă datele în sistem prin împărțirea acestora în blocuri
• De obicei, dimensiunea unui bloc este 64MB sau 128MB;// se folosește
redundanța împotriva defectelor!
• Task-urile se împart în două faze:
• Map: care se execută pe blocuri mici de date, acolo unde se află acestea
• Reduce: care combină datele pentru a produce rezultatul final
• Un program master care alocă task-urile pe noduri
• Detectează defectele și reasignează task-urile altor noduri
• Repornește un task fără a afecta nodurile care procesează alte blocuri de date
• Adaugă nodurile repornite la sistem și alocă task-urile noi
HDFS (Hadoop Distributed File System)
• Gestionează datele din cluster
• Împarte datele în blocuri și le distribuie la noduri
• Crează replici ale blocurilor de date
• Este implementat în Java și are la baza GFS de la Google
• Asigură capacități de stocare redundante pentru cantități mari de
date
• Funcționează cel mai bine cu un număr mic de fișiere mari
• Millioane și nu miliarde de fișiere
• De obicei, 100MB sau mai mult pentru un fișier
• Este optimizat pentru fluxuri de citire a fisierelor mari
Stocarea fișierelor
• Fișierele se împart în blocuri (en., blocks/chunks)
• La încărcarea fișierului în HDFS blocurile sunt distribuite/salvate pe
mai multe sisteme/mașini
• Se salvează blocuri diferite (ale aceluiași fișier) pe mai multe sisteme diferite
• Blocurile sunt duplicate/salvate pe mai multe mașini
• NameNode getionează modul în care a fost împărțit un fișier și
mașinile unde au fost salvate blocurile acestuia.
• Acest mod de lucru asigură caracterul distribuit al gestionării fișierelor și
asigură disponibilitatea datelor (i.e., a fisierelor) chiar dacă un sistem se
defectează.
Replicarea datelor
• Replicarea 3-fold [implicită]
Scenariu implicit/de bază
• Fișierul este împărțit în blocuri (dimensiunea implicită=64MB), iar
blocurile sunt replicate în cluster (implicit de 3 ori, fiecare bloc mai
are două copii).
• Acest scenariu este optimizat pentru:
• Rată de transfer (en., throughput) cât mai mare
• Operații Put/Get/Delete
• Operații Appends
• Replicarea blocurilor asigură:
• Durabilitatea
• Disponibilitatea
• Rata de transfer
Regăsirea datelor
• Atunci când un (cod) client dorește să regăsească date (i.e., un fișier)
• Trebuie să comunice cu NameNode pentru a detrmina care blocuri compun
fișierul și pe care noduri se află acestea.
• Apoi, clientul comunică direct cu nodurile de date pentru a citi datele
• Citirea se realizează transparent, clientul nu *stie* modul în care a fost
distribuit fișierul.
MapReduce: Distribuirea procesării între
noduri/sisteme
• MapReduce : Prezentare generală
• Fiecare nod procesează datele stocate local
• Cosntă din 2 faze principale
• Map
• Reduce

• MapReduce : Caracteristici
• Paralelizare și distribuție automată
• Toleranță la erori/defecțiuni (en., Fault-Tolerance)
• Oferă o mod de lucru abstract pe care programatorii îl pot utiliza cu ușurință
The Mapper
• Datele se citesc ca perechi cheie/valoare
• La cheie de obicei se renunță
• Răspunsul este sub forma perechilor cheie/valoare

Amestecă (en., shuffle) și sortează


• Răspunsul de la mapper este sortat după cheie
• Toate valorile cu aceeași cheie vor merge garantat pe aceeași mașină
The Reducer
• Este apelat o singură dată pentru fiecare cheie unică

• Primește o listă de valori cu cheia ca intrare

• Răspunsul este format din perechi cheie/valoare


• De obicei avem un singur răspuns pentru fiecare cheie primită ca intrare
Numărarea cuvintelor
Anatomia unui cluster
[care sunt modulele care compun un cluster Hadoop]
• NameNode
• Gestionează metadatele (informațiile despre sistem) pentru HDFS într-un fișier fsimage
• Secondary NameNode
• Rulează funcții de întreținere pentru NameNode (NU este un backup pentru NameNode)
• Actualizează fișierul fsimage
• DataNode
• Deține blocurile de date din HDFS
• JobTracker
• Administrează job-urile MapReduce determinând ordinea de execuție și alocarea task-urilor
individuale.
• TaskTracker
• Monitorizează (prin urmărirea performanței) task-urile individuale de tip Map si Reduce.
Ecosistemul Hadoop Ecosystem [alte unelte
disponibile]
• De obicei, administrarea Hadoop este incomodă
• Uneltele permit programatorilor să beneficieze de întreaga putere a sistemului
Hadoop.
• Hive
• Procesare de SQL în Hadoop
• Pig
• Procesare cu script-uri în
• Cascading
• Model de procesarte cu Pipe și Filter
• HBase
• Bază de date peste Hadoop
• Flume
• Unealtă proiectată pentru mutarea datelor mari
Architectura HDFS
Architectura HDFS
• NameNode și DataNode:
• Sunt aplicații software care rulează sub GNU/Linux.
• HDFS este scris în Java. Orice sistem care are JVM poate rula NameNode și
DataNode.
• De obicei, un sistem rulează un singur NameNode.
• Aspecte principale:
• Replicarea blocurilor
• Persistența metainformațiilor sistemului de fișiere
• NameNode folosește un fișier de log (i.e., EditLog) pentru a înregistra în mod persistent
schimbările care au loc în sistemul de fișiere (ex., crearea unui nou fișier, schimbarea
factorului de replicare, etc.)
• Fișierul FsImage urmărește alocarile de blocuri pe noduri și proprietățile sistemului.
Architectura HDFS
• Aspecte principale:
• Protocoale de comunicație: TCP/IP. Prin proiectare, un NameNode nu inițiază un
RPC (Remote Procedure Call), el doar răspunde la RPC-urile trimise de clienți.
• Defectarea discurilor: fiecare DataNode trimite periodic un mesaj Heartbeat către
NameNode.
• Echilibrarea cluster-ului: se realizează automat prim mutarea datelor de la
DataNode la altul.
• Integritatea datelor: un bloc de date dintr-un DataNode poate fi
• a block of data from a DataNode may become stricat (ex., erori pe HD, erori în
rețea, bug-uri software). HDFS implementează validarea cu ajutorul sumelor de
control.
• Data race: Politica implementată pentru fișiere este „write-once-read-many-times”.
Repere pentr instalarea și configurarea HDFS
• HDFS rulează în principal pe sisteme GNU/Linux.
• Rularea Hadoop in Windows:
• Necesită Cygwin (i.e., emulator de Linux), care nu este recomandat în producție.
• Pe o mașină virtuală cu Linux
• Cerințe SW: Java 1.6, Eclipse IDE și Cygwin (pentru Windows)
• Configurarea Hadoop:
• fs.default.name; dfs.data.dir; dfs.name.dir; etc.
• Trei moduri de lucry: Standalone, Pseudo-Distributed Mode, Fully-Distributed Mode
• Pentru dezvoltarea aplicațiilor se poate folosi Plug-in pentru Eclipse:
• Plug-in-ul este un fișier .jar care conține perspectiva *Map/Reduce*.
Utilizarea HDFS
• Start/stop HDFS
• Interacțiunea cu HDFS pentru încărcarea fisierelor și obținerea de informații
se face prin două module:
• dfs (i.e., FsShell): pentru lucrul cu fișiere
• dfsadmin pentru interogarea și lucrul cu sistemul
• Comenzi pentru dfs:
• Listarea fisierelor; crearea de directoare; încărcarea unui fișier; verificarea încărcării
corecte a unui fișier; încărcarea mai multor fișiere în paralel; obținerea de informații de
la HDFS; alte comenzi.
• Comenzi pentru dfsadmin:
• Obținerea unui raport al stării; trecerea în safemode (numai operații read-only)
Utilizarea HDFS în mod programatic
4: import org.apache.hadoop.conf.Configuration;
5: import org.apache.hadoop.fs.FileSystem;
8: import org.apache.hadoop.fs.Path;
10: public class HDFSHelloWorld {
15: public static void main (String [] args) throws IOException {
17: Configuration conf = new Configuration();
18: FileSystem fs = FileSystem.get(conf);
20: Path filenamePath = new Path(theFilename);
22: try {
23: if (fs.exists(filenamePath)) {
24: // remove the file first
25: fs.delete(filenamePath);
26: }
27:
28: FSDataOutputStream out = fs.create(filenamePath);
29: out.writeUTF(message);
30: out.close();
31:
32: FSDataInputStream in = fs.open(filenamePath);
Exemplu MapReduce
• https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/
hadoop-mapreduce-client-core/MapReduceTutorial.html
• MapReduce constă din:
• un singur master, și anume ResourceManager
• Un worker de tip NodeManager pentru fiecare nod, și
• MRAppMaster pentru toată aplicația (revizuiți ghidul arhitecturii YARN)
• Aplicațiile:
• Specifică locațiile pentru input/output
• Furnizează funcțiile map și reduce functions prin implementări ale interfețelor
corespunzătoare si/sau ale claselor abstracte.
• Și parametrii job-urilor sub forma unei configurații.
Exemplu MapReduce (cont.)
• Clentul Hadoop trimite job-ul (jar/executabil etc.) și configurația către
ResourceManager care își asumă responsabilitatea distribuirii
software-ului către workeri, programarea task-urilor și monitorizarea
lor, furnizarea de informații referitoare la stare și disgnostic pentru
job-ul client.
• Input și Output
• Sistemul MapReduce lucrează exclusiv cu perechi <cheie, valoare>
• (input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3,
v3> (output)
Exemplu WordCount
import org.apache.Hadoop.conf.Configuration, Job, Mapper, Reducer, etc…
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
Exemplu WordCount (cont.)
// the application
public class WordCount {

public static void main(String[] args) throws Exception {
}
}
Exemplu WordCount (cont.)
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();// one line a time
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);//Input (first map): <Hello, 1>; <World, 1>; <Bye, 1>; <World,1>
}
}
}
Exemplu WordCount (cont.)
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result); //Output (first map): <Hello, 1>; <World, 2>; <Bye, 1>
}
}

S-ar putea să vă placă și