Rezolvarea concurenta a problemelorsoftware.ucv.ro/~cbadica/scd/cap10.pdfRezolvarea concurenta a...

22
Kvadratni metar je kvadrat ~ija je stranica 1 metar. Jedan kvadratni metar 1 skra}eno zapisujemo m . 2 Povr{ se meri datom povr{i. 1 mm 1 cm 1 dm 1 dm 1m 1m 2 1. Ako u {koli imate model kvadratnog metra izmerite povr{ table i zapi{ite rezultat merewa u svesku. * 100 * 100 * 100 Jedinice za merewe povr{i su: mawe od kvadratnog metra kvadratni decimetar dm ( ) - kvadrat ~ija je stranica 1 decimetar. kvadratni centimetar cm ( ) - kvadrat ~ija je stranica 1 centimetar. kvadratni milimetar mm ( ) - kvadrat ~ija je stranica 1 milimetar. 2 2 2 1 mm 2 1 cm 2 1 dm 2 1 m 2 1 dm 2 1 cm 2 1m 2 U~imo. 45 Jedinice za povr{inu Jedinice za povr{inu Uo~ili smo da kada istu povr{ merimo razli~itim jedinicama mere dobijaju se razli~iti merni brojevi. Zbog toga za merewe povr{i koristimo nepromenqivu povr{ koja se zove . Wime merimo povr{i: podova i zidova prostorija, tepiha, tapeta ... kvadratni metar 1 cm Jedan zid u mojoj sobi ima povr{inu 9 . m 2

Transcript of Rezolvarea concurenta a problemelorsoftware.ucv.ro/~cbadica/scd/cap10.pdfRezolvarea concurenta a...

2018-2019

Rezolvarea concurenta a

problemelor

Capitolul 10

2018-2019

Inmultirea a doua matrici

โ€ข Se considera doua matrici ๐ด si ๐ต de dimensiuni ๐‘› ร— ๐‘› si se cere determinarea matricii produs ๐ถ = ๐ด ร— ๐ต unde elementele lui ๐ถ se determina cu:

๐‘๐‘–๐‘— = ๐‘Ž๐‘–๐‘˜ ร— ๐‘๐‘˜๐‘—

๐‘›โˆ’1

๐‘–=0

โ€ข Se observa ca elementele lui ๐ถ se pot determina concurent.

โ€ข Vom construi cate un fir Worker parametrizat cu valorile ๐‘– si ๐‘—, responsabil cu calculul fiecarei valori ๐‘๐‘–๐‘—.

โ€ข O clasa separata MMThread este responsabila cu construirea unei matrici ๐‘› ร— ๐‘› de obiecte Worker care lucreaza concurent pentru determinarea matricii ๐ถ.

โ€ข Pentru testare se foloseste JUnit 4 si clasa MMThreadTest.

2018-2019

Clasa Worker class Worker extends Thread {

int row, col;

Worker(int row, int col) {

this.row = row; this.col = col;

}

public void run() {

double dotProduct = 0.0;

System.out.println("Worker["+row+"]["+col+"]");

for (int i = 0; i < n; i++) {

dotProduct += a[row][i] * b[i][col];

}

c[row][col] = dotProduct;

}

}

Ce element

calculeaza acest fir

Calculul

propriuzis

2018-2019

Clasa MMThread I public class MMThread {

double[][] a, b, c;

int n;

public MMThread(double[][] a, double[][] b) {

n = a.length;

this.a = a;

this.b = b;

this.c = new double[n][n];

}

void multiply() {

Worker[][] worker = new Worker[n][n];

// create one thread per matrix entry

for (int row = 0; row < n; row++) {

for (int col = 0; col < n; col++) {

worker[row][col] = new Worker(row,col);

}

}

Creaza maatricea

de fire

2018-2019

Clasa MMThread II // start the threads

for (int row = 0; row < n; row++) {

for (int col = 0; col < n; col++) {

worker[row][col].start();

}

}

// wait for them to finish

for (int row = 0; row < n; row++) {

for (int col = 0; col < n; col++) {

try {

worker[row][col].join();

} catch (InterruptedException ex) {

ex.printStackTrace();

}

}

}

}

}

Porneste firele

Asteapta sa se

termine

2018-2019

Clasa MMThreadTest import org.junit.Test;

import org.junit.Assert;

public class MMThreadTest {

public MMThreadTest() { }

@Test public void testRun() {

System.out.println("run");

double[][] a = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};

double[][] b = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};

MMThread instance = new MMThread(a,b);

instance.multiply();

double[][] c = instance.c;

int n = a.length;

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

Assert.assertEquals(new Double(c[i][j]),

new Double(a[i][j]));

}

}

}

}

2018-2019

Dezavantaje ale abordarii naive

โ€ข Aparent solutia naiva maximizeaza concurenta.

โ€ข Pentru matrici mari se creaza foarte multe fire.

โ€ข Gestiunea firelor presupune costuri suplimentare:

โ€“ Memorie suplimentara pentru fiecare fir in parte

โ€“ Efort de calcul suplimentar pentru: โ€ข Crearea

โ€ข Planificarea

โ€ข Distrugerea firelor

โ€“ Raportul munca utila / effort suplimentar este scazut

โ€ข Crearea unui numar mare de fire avand fiecare o viata

foarte scurta (adica se executa doar pentru putin timp) este

o metoda ineficienta de a organiza o aplicatie de calcul

concurent !

2018-2019

Rezerve de fire

โ€ข O metoda mai eficienta de a organiza o aplicatie de calcul concurent este de a crea o rezerva de fire (engl. thread pool).

โ€ข Firele din rezerva reprezinta astfel resurse durabile (engl. long-lived resources) de calcul ele putand fi (re)alocate in mod repetitiv unor sarcini de calcul de scurta durata.

โ€ข Rezerva de fire evita costul suplimentar de creare / distrugere repetata de fire. Acesta poate fi semnificativ in cazul in care cererea de calcul contine multe sarcini de scurta durata.

2018-2019

Rezerve de fire = abstractizare

โ€ข Pe platformele multi-procesor rezervele de fire pot fi

dependente de platforma in scopul eficientizarii

implementarii.

โ€ข O rezerva de fire ofera un beneficiu dpdv al

modularizarii aplicatiei:

Programatorul este degrevat de cunoasterea detaliilor

specifice platformei, programul putand rula pe

diverse platforme uni- respectiv multi-procesor.

2018-2019

Sarcini de lucru in Java

โ€ข O sarcina de lucru ce NU intoarce nici un rezultat se

reprezinta printr-un obiect Runnable.

public interface Runnable {

void run();

}

โ€ข O sarcina de lucru ce intoarce un rezultat generic de tip

๐‘‡ se reprezinta printr-un obiect Callable<T>.

public interface Callable<T> {

T call() throws Exception;

}

2018-2019

Executori

โ€ข O aplicatie complexa este descompusa in sarcini logice

de calcul (engl. execution tasks). Obiectele ce

abstractizeaza executia sarcinilor unei aplicatii se

numesc executori si se implementeaza prin interfata

Executor.

โ€ข In aplicatiile complexe se urmareste separarea

gestiunii firelor de logica aplicatiei. Firele ofera un

mecanism de executie concurenta si asincrona a

sarcinilor. Firele pot fi privite ca resurse disponibile

pentru executia sarcinilor.

2018-2019

Exemple de executori

โ€ข Spre exemplu, putem defini doua modalitati simple de

alocare a sarcinilor pe fire de executie:

โ€“ Alocarea secventiala, cand toate sarcinile se aloca unui

singur fir de executie. Are dezavantajul ca nu foloseste

optim resursele disponibile si ofera un timp de raspuns

necorespunzator si o reactivitate scazuta

โ€“ Alocarea sarcina-pe-fir, cand fiecare sarcina se aloca pe un

fir separat. Am vazut ca aceasta metoda sufera de problema

unei gestiuni necorespunzatoare a resurselor diponibile,

deoarece fragmenteaza excesiv aceste resurse.

2018-2019

Interfata Executor I

โ€ข Abstractizeaza executia unei sarcini de executie in Java.

public interface Executor {

void execute(Runnable command);

}

โ€ข Permite executia asincrona a sarcinilor intr-o varietate de

politici de executie. Decupleaza activitatea de trimitere a unei

sarcini spre executie (engl. task submission) de executia

propriuzisa a sarcinii (engl. task execution).

โ€ข Executorii se bazeaza pe sablonul producator-consumator. โ€“ O activitate care trimite sarcini spre executie este un producator de

unitati de lucru,

โ€“ Firele care in final executa sarcinile sunt consumatori. Executia unei

sarcini este abstractizata ca activitate de consumare a unei sarcini.

2018-2019

Interfata Executor II

โ€ข Daca r este un obiect Runnable ce descrie o sarcina de executie, in loc de a asocia direct sarcina unui fir astfel:

(new Thread(r)).start();

se va folosi un obiect e ce implementeaza interfata Executor:

e.execute(r);

โ€ข A doua metoda nu expliciteaza modul in care se va aloca un fir lucrator (engl. worker thread) sarcinii r si astfel nu se creaza o legatura directa intre sarcina si firul alocat.

โ€ข Politica alocarii firului lucrator nu este specificata explicit. Decuplarea trimiterii unei sarcini de executia sa permite schimbarea relativ usoara a politicii de executie a unei multimi de sarcini.

2018-2019

Politici de executie

โ€ข O politica de executie (engl. execution policy) specifica

ce, unde, cand si cum se va executa o sarcina: โ€“ In ce fir se va executa sarcina respectiva ?

โ€“ In ce ordine se vor executa sarcinile: FIFO, LIFO, prioritati, etc ?

โ€“ Cate sarcini se pot executa concurent ?

โ€“ Cate sarcini pot fi pastrate in coada inainte de inceperea executiei

?

โ€“ Daca executia unei sarcini trebuie sa fie rejectata deoarece

sistemul este supraincarcat atunci ce sarcina va fi aleasa drept

victima si cum trebuie aplicatia instiintata de acest lucru ?

โ€“ Ce actiuni trebuie intreprinse inainte / dupa executia unei sarcini ?

2018-2019

Politica optimala

โ€ข Politica optimala depinde de: โ€“ resursele de calcul existente

โ€“ cerintele de calitate a serviciului.

โ€ข De exemplu, prin limitarea numarului de executii

concurente ne putem asigura de faptul ca: โ€“ aplicatia nu va esua datorita epuizarii resurselor

โ€“ nu isi va degrada necorespnzator perfomantele prin

epuizarea resurselor, datorita fragmentarii exagerate a

acestora

2018-2019

Exemple de politici simple de executie

โ€ข Politica de a crea un nou fir pentru fiecare sarcina alocata, de a aloca

imediat sarcina acestui fir si apoi a-l lansa in executie se poate

implementa astfel:

public class ThreadPerTaskExecutor implements Executor {

public void execute(Runnable r) {

(new Thread(r)).start();

};

}

โ€ข Executarea unei sarcini in firul apelantului se poate specifica astfel:

public class WithinThreadExecutor implements Executor {

public void execute(Runnable r) {

r.run();

};

}

2018-2019

Executori pentru rezerve de fire in Java

โ€ข Java dispune de executori speciali reprezentati prin

interfata java.util.ExecutorService. Ei asigura: โ€“ Implementarea unei rezerve de fire cu o politica specifica de

alocare

โ€“ Executia asincrona a unei multimi de sarcini

public interface ExecutorService extends Executor {

<T> Future<T> submit(Callable<T> task);

Future<?> submit(Runnable task);

<T> Future<T> submit(Runnable task, T result);

}

2018-2019

Executia asincrona a sarcinilor

โ€ข Orice sarcina ce trebuie trimisa spre executie la un

executor are un ciclu de viata ce contine fazele: โ€“ creare,

โ€“ trimitere,

โ€“ startare,

โ€“ terminare.

โ€ข Executia sarcinii dureaza un timp nenul.

โ€ข Executia are loc in mod asincron (concurent) cu firul

apelant.

2018-2019

Interfata Future<T>

โ€ข Pentru reprezentarea ciclului de viata al sarcinii se foloseste un

obiect Future<T>. Interfata Future este definita in pachetul

java.util.concurrent astfel:

public interface Future<T> {

boolean cancel(boolean mayInterruptIfRunning);

T get() throws InterruptedException, ExecutionException,

CancellationException;

boolean isCancelled();

boolean isDone();

}

โ€ข Un Future care nu intoarce o valoare este reprezentat prin

Future<?>

Intoarce rezultatul sarcinii.

Apelul blocheaza apelantul daca rezultatul nu este gata.

2018-2019

Interfete functionale si Lambda expresii

โ€ข O interfata ce contine o singura metoda = interfata functionala.

โ€ข Exemple: Runnable, Callable.

โ€ข Java permite definirea facila a obiectelor ce implementeaza interfete

functionale folosind Lambda expresii. public class RunnableTest {

public static void main(String[] args) {

System.out.println("=== RunnableTest ===");

// Anonymous Runnable

Runnable r1 = new Runnable(){

@Override public void run(){

System.out.println("Hello world one!");

}

};

// Lambda Runnable

Runnable r2 = () -> System.out.println("Hello world two!");

// Run them!

r1.run();

r2.run();

}

}

2018-2019

Crearea rezervelor de fire

โ€ข O rezerva de fire este accesibila printr-un obiect executor ce

implementeaza interfata ExecutorService:

โ€“ ThreadPoolExecutor

โ€“ ScheduledThreadPoolExecutor

โ€ข Crearea unei rezerve de fire se poate realiza:

โ€“ Varianta simpla: folosind metodele factory ale clasei Executors, ce

permit crearea unor rezervee de fire preconfigurate.

โ€“ Varianta complicata: folosind constructorii claselor executor:

ThreadPoolExecutor si ScheduledThreadPoolExecutor

โ€ข Exemple: ExecutorService exServ1 = Executors.newSingleThreadExecutor();

ExecutorService exServ2 = Executors.newFixedThreadPool(10);

ExecutorService exServ3 = Executors.newScheduledThreadPool(10);

2018-2019

Rezerve de fire predefinite โ€“ metode factory

public static ExecutorService

newFixedThreadPool(int nThreads)

public static ExecutorService

newCachedThreadPool()

public static ExecutorService

newSingleThreadExecutor()

2018-2019

newFixedThreadPool

public static ExecutorService

newFixedThreadPool(int nThreads)

โ€ข Creaza o rezerva de fire avand un numar dat nThreads

de fire organizate intr-o coada circulara. Firele sunt

create la trimiterea sarcinilor. Daca se trimit sarcini

peste numarul maxim de fire active, sarcinile in plus

vor astepta in coada pana la eliberarea unui fir. Daca

un fir se termina prematur, inainte de a se incheia

executia unei sarcini, se va crea un alt fir (doar daca

este necesar).

2018-2019

newCachedThreadPool

public static ExecutorService

newCachedThreadPool()

โ€ข Creaza o rezerva de fire ce va crea fire pe masura ce

acest lucru este cerut de aplicatie. Firele existente se

refolosesc ori de cate ori este nevoie, altfel se creaza

un nou fir. Firele care nu sunt folosite timp de 1 minut

sunt terminate si eliminate. Astfel ca o rezerva ce nu

este folosita timp indelungat nu va consuma resurse

suplimentare.

2018-2019

newSingleThreadExecutor

public static ExecutorService

newSingleThreadExecutor()

โ€ข Creaza un executor pentru o rezerva de fire cu un

singur fir lucrator, acesta putand fi inlocuit daca se

termina prematur. Executorul foloseste o coada pentru

pastrarea firelor in asteptare. Se garanteaza executia

secventiala a sarcinilor, pe masura sosirii lor.

2018-2019

ScheduledExecutorService

โ€ข ScheduledExecutorService este o interfata ce descrie

executori care planifica executia sarcinilor astfel: โ€“ Executie dupa o anumita intarziere

โ€“ Executie periodica la un interval prestabilit intre doua

executii succesive public interface ScheduledExecutorService extends

ExecutorService {

<V> ScheduledFuture<V> schedule(Callable<V> command,

long delay, TimeUnit unit);

ScheduledFuture<?> schedule(Runnable command,

long delay, TimeUnit unit);

ScheduledFuture<?> scheduleAtFixedRate(Runnable command,

long initialDelay, long period, TimeUnit unit);

ScheduledFuture<?> scheduleAtFixedDelay(...);

}

2018-2019

newScheduledThreadPool

public static ScheduledExecutorService

newScheduledThreadPool(int corePoolSize)

โ€ข Creaza un executor pentru o rezerva de fire cu un

numar fix de fire lucratoare ce permite executarea

planificata a sarcinilor.

2018-2019

Adunarea matricilor folosind divide-et-impera

โ€ข Se considera ca matricile sunt patrate de dimensiune ๐‘› = 2๐‘˜. Orice matrice ๐ด โˆˆ ๐‘› ร— ๐‘› se descompune astfel:

๐ด =๐ด00 ๐ด01๐ด10 ๐ด11

unde matricile ๐ด๐‘–๐‘— โˆˆ๐‘›

2ร—๐‘›

2= 2๐‘˜โˆ’1 ร— 2๐‘˜โˆ’1

โ€ข Adunarea de matrici ๐ถ = ๐ด + ๐ต se descompune astfel:

๐ถ00 ๐ถ01๐ถ10 ๐ถ11

=๐ด00 ๐ด01๐ด10 ๐ด11

+๐ต00 ๐ต01๐ต10 ๐ต11

=

๐ด00 + ๐ต00 ๐ด01 + ๐ต01๐ด10 + ๐ต10 ๐ด11 + ๐ต11

โ€ข Rezulta ca cele 4 sume ๐ด๐‘–๐‘— + ๐ต๐‘–๐‘— se pot realiza concurent.

2018-2019

Reprezentarea matricilor

โ€ข O matrice se reprezinta prin clasa Matrix ce contine: โ€“ Dimensiunea reprezentata prin campul dim;

โ€“ Deplasamantul pe linii si pe coloane al elementului din stanga sus al

matricii: rowDisplace si colDisplace. Aceste valori sunt necesare

pentru a accesa elementele matricilor ๐ด01, ๐ด10 si ๐ด11. โ€“ Un tablou bidimensional data cu elementele matricii

โ€ข Exista doi constructori: โ€“ Crearea unei matrici ๐‘‘ ร— ๐‘‘ โ€“ Crearea si initializarea unei matrici pe baza unui tablou bidimensional

โ€ข O matrice dispune de metode pentru: โ€“ Determinarea dimensiunii getDim()

โ€“ Citirea / scrierea unui element (row,col); acesta se afla pe linia

row+rowDisplace si pe coloana col+colDisplace.

โ€ข O matrice se descompune folosind metoda split() in 4

submatrici patrate de dimensiuni egale.

2018-2019

Clasa Matrix I private static class Matrix {

int dim;

double[][] data;

int rowDisplace, colDisplace;

Matrix(int d) {

dim = d;

rowDisplace = colDisplace = 0;

data = new double[d][d];

}

Matrix(double[][] matrix, int x, int y, int d) {

data = matrix;

rowDisplace = x; colDisplace = y;

dim = d;

}

double get(int row, int col) {

return data[row+rowDisplace][col+colDisplace];

}

void set(int row, int col, double value) {

data[row+rowDisplace][col+colDisplace] = value;

}

2018-2019

Clasa Matrix II int getDim() { return dim; }

Matrix[][] split() {

Matrix[][] result = new Matrix[2][2];

int newDim = dim / 2;

result[0][0] = new Matrix(data, rowDisplace, colDisplace,

newDim);

result[0][1] = new Matrix(data, rowDisplace,

colDisplace + newDim, newDim);

result[1][0] = new Matrix(data, rowDisplace + newDim,

colDisplace, newDim);

result[1][1] = new Matrix(data, rowDisplace + newDim,

colDisplace + newDim, newDim);

return result;

}

}

2018-2019

Realizarea adunarii

โ€ข Task-ul de adunare AddTask primeste matricile: operanzii a si b,

respectiv rezultatul c. Fie n dimensiunea operanzilor.

โ€ข Daca n = 1 atunci matricile sunt scalari si adunarea este scalara.

โ€ข Daca n > 1 atunci se descompun matricile aa, bb, si cc.

โ€ข Se realizeaza apoi adunarea concurent, pe fiecare bloc, folosind

task-ul AddTask, pentru fiecare bloc preluat din aa, bb si cc.

โ€ข Pentru realizarea calculelor se foloseste o rezerva de fire.

โ€ข AddTask este o clasa separata ce implementeaza interfata

Runnable.

โ€ข Obtinerea rezultatului foloseste o matrice de obiecte Future<?>.

โ€ข Matricea este creata separat, apoi este initializata in urma

operatiei submit() de trimitere a sarcinilor spre executie.

Asteptarea terminarii calculelor se realizeaza invocand metoda

get() a clasei Future.

2018-2019

Clasa MatrixTask import java.util.concurrent.*;

public class MatrixTask {

static ExecutorService exec = Executors.newCachedThreadPool();

static Matrix add(Matrix a, Matrix b)

throws InterruptedException, ExecutionException {

int n = a.getDim();

Matrix c = new Matrix(n);

Future<?> future = exec.submit(new AddTask(a, b, c));

future.get();

return c;

}

static class AddTask implements Runnable {

// ...

}

}

2018-2019

Clasa AddTask static class AddTask implements Runnable {

Matrix a, b, c;

public AddTask(Matrix a, Matrix b, Matrix c) {

this.a = a; this.b = b; this.c = c;

}

public void run() {

try {

int n = a.getDim();

if (n == 1) {

c.set(0, 0, a.get(0,0) + b.get(0,0));

} else {

Matrix[][] aa = a.split(), bb = b.split(), cc = c.split();

Future<?>[][] future = (Future<?>[][]) new Future[2][2];

for (int i = 0; i < 2; i++)

for (int j = 0; j < 2; j++)

future[i][j] = exec.submit(new AddTask(aa[i][j],

bb[i][j], cc[i][j]));

for (int i = 0; i < 2; i++)

for (int j = 0; j < 2; j++) future[i][j].get();

}

} catch (Exception ex) { ex.printStackTrace(); }

}

}

Parallel calls

Pick up & combine results

2018-2019

Inmultirea matricilor folosind divide-et-impera

โ€ข Inmultirea de matrici ๐ถ = ๐ด ร— ๐ต se descompune astfel:

๐ถ00 ๐ถ01๐ถ10 ๐ถ11

=๐ด00 ๐ด01๐ด10 ๐ด11

ร—๐ต00 ๐ต01๐ต10 ๐ต11

= ๐ด00 ร— ๐ต00 + ๐ด01 ร— ๐ต10 ๐ด00 ร— ๐ต01 + ๐ด01 ร— ๐ต11๐ด10 ร— ๐ต00 + ๐ด11 ร— ๐ต10 ๐ด10 ร— ๐ต01 + ๐ด11 ร— ๐ต11

โ€ข Cele 8 produse ๐ด๐‘–๐‘— ร— ๐ต๐‘˜๐‘™ se pot realiza in mod concurent.

Apoi, cele 4 sume ๐ด๐‘–0 ร— ๐ต0๐‘— + ๐ด๐‘–1 ร— ๐ต1๐‘— se pot realiza in

paralel.

โ€ข Tema: sa se implementeze clasa MulTask, dupa modelul clasei

AddTask, care realizeaza inmultirea celor doua matrici.

2018-2019

Determinarea numerelor lui Fibonacci

โ€ข Pentru exemplificarea sarcinilor ce intorc o valoare folosind

Callable<T>, consideram calculul termenilor sirului Fibonacci:

๐น๐‘› = 1 if ๐‘› = 01 if ๐‘› = 1

๐น๐‘›โˆ’1 + ๐น๐‘›โˆ’2 if ๐‘› โ‰ฅ 2

โ€ข Se creaza o sarcina FibTask care implementeaza interfata

Callable<Integer>.

โ€ข Aceasta abordare de calcul a numerelor lui Fibonacci este

foarte ineficienta !

โ€ข Tema: De ce?

โ€ข Tema: Sa se realizeze o implementare eficienta.

2018-2019

Clasa FibTask import java.util.concurrent.*;

public class FibTask implements Callable<Integer> {

static ExecutorService exec = Executors.newCachedThreadPool();

int arg;

public FibTask(int n) {

arg = n;

}

public Integer call() {

try {

if (arg > 2) {

Future<Integer> left = exec.submit(new FibTask(arg-1));

Future<Integer> right = exec.submit(new FibTask(arg-2));

return left.get() + right.get();

} else {

return 1;

}

} catch (Exception ex) {

ex.printStackTrace();

return 1;

}

}

}

2018-2019

Analiza concurentei

โ€ข Un calcul multifir se poate vizualiza grafic pe un graf orientat

aciclic.

โ€ข Nod = pas de calcul (orice instructiune este un pas de calcul).

โ€ข Arc = o dependenta intre un nod predecesor si un nod succesor.

โ€ข Un fir este practic o secventa de noduri, astfel incat fiecare

instructiune a sa este un pas separat de calcul. Fiecare nod

depinde de predecesorul sau din secventa.

โ€ข Un nod care creaza un obiect Future are 2 succesori: โ€“ Un nod succesor in acelasi fir

โ€“ Un nod succesor care este primul nod asociat sarcinii de calcul

asincrone corespunzatoaare obiectului Future.

โ€ข Pentru fiecare operatie get() a unui obiect Future se creaza un

arc de la ultimul nod al sarcinii asincrone asociate obiectului

catre nodul corespunzator invocarii operatiei get().

2018-2019

Graful de dependenta pentru FibTask

โ€ข Se observa ca graful de dependente mimeaza arborele de apeluri recursive pentru calculul termenului lui Fibonacci de ordin n.

โ€ข Diferenta fata de implementarea secventiala este faptul ca sarcinile determinate de subarborii corespunzatori calculului termenilor ๐น๐‘›โˆ’1 si ๐น๐‘›โˆ’2 se executa concurent, in fire separate.

Sursa: Herlithy & Shavit, 2012

2018-2019

Graful de dependenta pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

2018-2019

Graful de dependenta pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

fib(3)

2018-2019

Graful de dependenta pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

fib(3) fib(2)

fib(2)

2018-2019

Graful de dependenta pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

fib(3) fib(2)

fib(2) fib(1) fib(1) fib(1)

fib(1) fib(1)

2018-2019

Graful de dependenta pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

fib(3) fib(2)

call get

fib(2) fib(1) fib(1) fib(1)

fib(1) fib(1) Graful de dependente mimeaza arborele de apeluri

recursive.

Diferenta fata de cazul secvential este ca, calculul

๐น๐‘›โˆ’1 si ๐น๐‘›โˆ’2 se executa concurent in fire separate.

2018-2019

Timp de executie

โ€ข Fie ๐‘‡๐‘ timpul minim necesar executarii unui program concurent pe un sistem cu ๐‘ procesoare.

โ€ข ๐‘‡๐‘ este o valoare ideala, fiind practic o margine

inferioara pentru timpul real de executie al unui program concurent.

โ€ข ๐‘‡1 este timpul de executie pe un singur procesor โ€“ computationโ€™s work

โ€ข ๐‘‡โˆž= lungimea caii critice (engl. critical path length)

2018-2019

Timpii pentru FibTask

Sursa: Herlithy & Shavit, 2012

fib(4)

fib(3) fib(2)

call get

fib(2) fib(1) fib(1) fib(1)

fib(1) fib(1) ๐‘ค๐‘œ๐‘Ÿ๐‘˜ = ๐‘‡1 = 17 ๐‘. ๐‘. ๐‘™. = ๐‘‡โˆž = 8

2018-2019

Limitari naturale

๐‘‡๐‘ โ‰ฅ๐‘‡1๐‘

โ€ข Exprima faptul ca intr-o singura cuanta temporala nu se pot face mai mult de ๐‘ pasi de calcul intrucat exista doar ๐‘ procesoare disponibile.

๐‘‡๐‘ โ‰ฅ ๐‘‡โˆž

โ€ข Timpul de calcul pentru un numar finit de procesoare nu poate fi mai bun decat timpul โ€œidealโ€ daca am avea o infinitate de procesoare.

2018-2019

Factorul de accelerare โ€ข Factorul de accelerare (engl. speedup) pentru executia pe ๐‘

procesoare:

๐‘†๐‘ =๐‘‡1

๐‘‡๐‘

โ€ข Daca o fractiune ๐‘ โˆˆ [0,1] din program se poate executa concurent atunci:

๐‘‡๐‘ = 1 โˆ’ ๐‘ +๐‘

๐‘

๐‘†๐‘ =1

1 โˆ’ ๐‘ +๐‘๐‘

=๐‘

1 + ๐‘ โˆ’ 1 ร— 1 โˆ’ ๐‘โ‰ค ๐‘

โ€ข Definitie. Un program concurent are accelerare liniara (engl. linear speedup) dnd ๐‘†๐‘ = ฮ˜ ๐‘ .

2018-2019

Analiza operatiilor concurente cu matrici: cazul ideal

โ€ข Fie ๐ด๐‘ ๐‘› numarul de pasi necesar adunarii a doua matrici

๐‘› ร— ๐‘› pe ๐‘ procesoare.

๐ด1 ๐‘› = 4 ร— ๐ด1๐‘›

2+ ฮ˜ 1 = ฮ˜(๐‘›2)

๐ดโˆž ๐‘› = ๐ดโˆž๐‘›

2+ ฮ˜ 1 = ฮ˜(log ๐‘›)

โ€ข Tema: Fie ๐‘€๐‘ ๐‘› numarul de pasi necesar inmultirii a doua

matrici ๐‘› ร— ๐‘› pe ๐‘ procesoare. Se cere:

๐‘€1 ๐‘› =?

๐‘€โˆž ๐‘› =?

De ce?

De ce?

2018-2019

Concurenta pe sisteme multiprocesor reale

โ€ข Sistemele de operare actuale permit descompunerea unei

aplicatii intr-o multime de fire la nivel de aplicatie / utilizator.

โ€ข Nucleul sistemului de operare dispune de un planficator care

gestioneaza alocarea si executia firelor pe procesoarele fizice

ale sistemului.

โ€ข Din punctul de vedere al dezvoltatorului de programe, o

aplicatie concurenta este conforma unui model pe trei niveluri:

โ€“ Nivelul logic, la care aplicatia este descompusa intr-o multime de

sarcini (engl.task)

โ€“ Nivelul intermediar, la care un planificator (la nivel de utilizator)

planifica si aloca sarcinile pe un numar finit de fire la nivel utilizator

โ€“ Nivelul fizic, la care planificatorul din nucleul sistemului de operare

planifica si aloca firele utilizator pe procesoarele sistemului

2018-2019

Ierarhia de planificare

Nivel logic โ€“ sarcini (tasks)

Nivel intermediar โ€“ fire (threads)

Nivel fizic โ€“ procesoare

User-level scheduler

Kernel-level scheduler

2018-2019

Analiza concurentei pe sisteme reale

โ€ข La un moment dat, pe un sistem cu N procesoare, un numar 0 โ‰ค ๐‘๐‘– โ‰ค ๐‘ fire utilizator sunt alocate de nucleul SO pentru a executa concurent cate un pas de calcul. Numarul mediu de procesoare disponibile pentru a executa concurent cate un pas de calcul la fiecare moment de timp pe un interval de ๐‘‡ pasi este:

๐‘๐ด =1

๐‘‡ร— ๐‘๐‘–๐‘‡โˆ’1๐‘–=0 .

โ€ข Se urmareste obtinerea unei accelerari proportionale cu valoarea medie ๐‘๐ด โ‰ค ๐‘.

2018-2019

Planificari โ€œgreedyโ€

โ€ข O planificare este greedy dnd planifica โ€œcat de mult

poateโ€. Aceasta inseamna ca numarul de pasi executati

la fiecare moment de timp ๐‘– este egal cu minimul dintre

numarul de procesoare disponibile ๐‘›๐‘– si numarul de

noduri gata de executie (noduri al caror pas curent este

gata de executie) din graful programului.

โ€ข Observatie: Orice planificare optimala este greedy dar

reciproca nu este neaparat adevarata. De ce?

2018-2019

Limita superioara a timpului de executie

โ€ข Teorema. Orice program concurent avand efortul de

calcul ๐‘‡1, lungimea caii critice ๐‘‡โˆž ce are la dispozitie

๐‘ fire utilizator se va executa pe orice planificare

greedy intr-un timp:

๐‘‡ โ‰ค๐‘‡1

๐‘๐ด+๐‘‡โˆžร— ๐‘โˆ’1

๐‘๐ด

2018-2019

Ideea demonstratiei

โ€ข Din definitia:

๐‘๐ด =1

๐‘‡ร— ๐‘๐‘–๐‘‡โˆ’1๐‘–=0

rezulta:

๐‘‡ =1

๐‘๐ดร— ๐‘๐‘–๐‘‡โˆ’1๐‘–=0

โ€ข Ramane sa aratam ca: ๐‘๐‘–๐‘‡โˆ’1๐‘–=0 โ‰ค ๐‘‡1 + ๐‘ โˆ’ 1 ๐‘‡โˆž

โ€ข Pasii de calcul sunt: i) de executie (work) cand sarcina se poate executa si ii) de asteptare (idle) cand sarcina a fost alocata dar nu se poate executa deoarece exista dependente fata de alte sarcini ce nu s-au terminat.

โ€ข La fiecare pas se executa cel putin o sarcina, deci pot astepta cel mult ๐‘ โˆ’ 1 sarcini pentru fiecare element al caii critice, deci in total sunt maxim ๐‘ โˆ’ 1 ๐‘‡โˆž pasi de asteptare. Totodata exista ๐‘‡1 pasi executati. Adunand rezulta c.c.t.d.

2018-2019

Optimalitate

โ€ข Observatie. Marginea superioara ๐‘‡1

๐‘+ ๐‘‡โˆž a oricarei planificari

greedy este cel mult egala cu dublul timpului optim de

planificare a executiei programului pe ๐‘ procesoare.

โ€ข Fie ๐‘‡๐‘โˆ— timpul optim. Avem:

๐‘‡๐‘โˆ— โ‰ฅ๐‘‡1๐‘

si

๐‘‡๐‘โˆ— โ‰ฅ ๐‘‡โˆž

de unde concluzia ca timpul optim este cel putin jumatate din ๐‘‡1

๐‘+ ๐‘‡โˆž decurge trivial.