Algoritmi paralleli di ordinamento
Lezione n°15
Algoritmi Avanzati
a.a.2011/2012
Prof.ssa Rossella Petreschi
Algoritmo pari/dispari
L’idea base è quella di far lavorare prima tutti i processori di indice pari e
poi quelli di indice dispari per evitare letture e scritture concorrenti nei
confronti.
0
• for s = 1 to n/2 do
• for i = 0 to i < n-1 step 2 pardo
•Pi:
if x[i] > x[i+1] then swap(x[i], x[i+1])
• for i = 1 to i < n-1 step 2 pardo
•Pi:
if x[i] > x[i+1] then swap(x[i], x[i+1])
1
3
4
5
6
s=1 pari
8 5 9 2 4 3 6
s=1 dispari
5 8 2 9 3 4 6
s=2 pari
5 2 8 3 9 4 6
s=2 dispari
2 5 3 8 4 9 6
s=3 pari
2 3 5 4 8 6 9
s=3 dispari
2 3 4 5 6 8 9
Fine
2 3 4 5 6 8 9
•Richiede tempo O(n) su una PRAM EREW con O(n) processori.
•Il costo complessivo è O(n2).
AA 2011-2012
2
Algoritmo pari/dispari
con p < n processori
Ogni processore Pi gestisce un blocco Si composto di b = n/p elementi.
Pi: ordina Si in modo sequenziale
for s = 0 to p/2 do
for i = 0 to i < p-1 step 2 pardo
Pi:
Si' = Merge(Si, Si+1)
Si = Si' [0, b-1]
Si+1 = Si' [b, 2b-1]
for i = 1 to i < p-1 step 2 pardo
Pi:
Si' = Merge(Si, Si+1)
Si = Si' [0, b-1]
Si+1 = Si' [b, 2b-1]
for i = 0 to p-1 pardo
I tempo richiesto è Tp = O(n/p log (n/p)) + p/2 O(n/p). Quando abbiamo
p = O(log n) il tempo Tp diventa O(n): in tal caso il costo totale è O(n log n).
AA 2011-2012
Ordinamento su PRAM CRCW
Sfruttiamo la scrittura concorrente per ottenere un semplice algoritmo di
ordinamento. Assumiamo una PRAM CRCW con scrittura concorrente
della somma dei valori scritti.
xiniz
for i = 0 to n-1 pardo
for j = 0 to n-1 pardo
Pi,j:
if (x[ i ] > x[ j ]) or
(x[ i ] = x[ j ] and i > j) then
c[ i ] = 1
for i = 0 to n-1 pardo
Pi,1: x[ c[ i ] ] = x[ i ]
7
5
3
8
5
0
1
2
3
4
i
1
2
3
4
AA 2011-2012
1
2
3
4
0
1
2
3
4
xfin
3
5
5
7
8
Risultati dei confronti
effettuati
0
Con n2 processori il tempo richiesto è O(1),
Il costo totale è quindi O(n2).
0
C
3
1
0
4
2
j
0
1
2
3
4
F
F
F
V
F
V
F
F
V
V
V
V
F
V
V
F
F
F
F
F
V
F
F
V
F
Il concetto di rango
Dati X = (x1, x2, …, xt), Y = (y1, y2, …, ys) e z, con z, xi e yj
nello stesso insieme U, definiamo:
1. rango(z:X) = numero di elementi in X  z
Es: X = (-3,8,-2,5), z = 1, rango(z:X) = 2
2. rango(Y:X) = (r1, r2, … rs) con ri = rango(yi:X)
Es: X = (15,-3,12,1,-5), Y = (3,-13,-2), rango(Y:X) = (3,0,2)
Si noti inoltre che vale la relazione*:
rango(x:AB) = rango(x:A) + rango(x:B)
*Nota: per semplicità assumiamo che i valori siano tutti distinti.
AA 2011-2012
Fondere tramite rango
Il problema di fondere due vettori ordinati A e B in un unico
vettore C si può risolvere calcolando il rango degli elementi di A
rispetto a B e di quelli di B rispetto ad A: rango(x:AB) è
esattamente la posizione in cui l’elemento x si trova nel vettore C.
L’algoritmo di Ricerca Binaria può essere utilizzato per calcolare il
rango di un elemento in un vettore, ed in particolare il rango di
ogni elemento di A in B e viceversa. Con tali informazioni
possiamo calcolare C.
Esempio: A=(-2, -1, 8) B=(3,6) AB=(-2, -1, 8, 3, 6)
rango(A:B)=(0, 0, 2) rango(B:A)=(2,2)
rango=(1, 2, 5, 3, 4) C=(-2, -1, 3, 6, 8)
AA 2011-2012
Inserire una sequenza “breve”
in un vettore ordinato
Siano X=(x1, x2, …, xn) un vettore ordinato e Y=(y1, …, ym)
una sequenza di valori qualunque tale che m=O(ns) con
0≤s≤1.
Utilizziamo un numero di processori pari ad N=n/m = (n1-s).
È possibile inserire ciascun valore yi nella sequenza
X determinando rango (yi:X) in tempo
O(m log2(n+2) / log2(N+1))
Quando m << n (ovvero se s  0) si ha N=O(n) e tempo O(1).
Quando m=O(n) (s  1) si ha N=O(1) e tempo O(n log n).
AA 2011-2012
Algoritmo di fusione tramite rango
Input: A=(a1, a2, …, an), B=(b1, b2, …, bm), ordinati in modo
crescente (m≤n).
Output: C=(c1, c2, …, cn+m), ordinato in modo crescente.
Idea: si partiziona il vettore B in (m / log m) blocchi
consecutivi di log(m) elementi ciascuno B0, B1, … e si crea il
vettore Y costituito dall’insieme degli elementi massimi dei
blocchi.
Si calcola rango(Y:A)=(r1, r2, …, rm) e si divide A in blocchi
consecutivi A0=(a1, …, ar ), A1=(ar +1, …, ar ), …
1
1
2
Poiché A0 e B0 contengono elementi minori di tutti gli altri elementi di A
e B, fondendo A0 e B0 tramite rango si ottiene la sequenza ordinata dei
primi elementi di C. Iterando il ragionamento su tutte le coppie Ai e Bi si
ottiene l’intero vettore C ordinato.
AA 2011-2012
Complessità dell’algoritmo di fusione
Per completare la fusione di A e B (cioè per calcolare la posizione di
ciascun valore nel vettore finale) si dovranno fondere tutte le coppie di
sottovettori (Ai, Bi). Utilizzando l’inserimento di una seq. breve in un vettore
ordinato ciò si può fare in O(log m) tempo con |Ai| processori. Quindi con n
processori tutte le coppie possono essere fuse contemporaneamente in
tempo logaritmico.
Bisogna inoltre tener conto di quanti elementi ci sono in tutti gli Aj e Bj (j<i)
per posizionare gli elementi di Ai e Bi nel vettore risultante C:
• il valore r[i] (indice di inizio di Ai) da il contributo totale degli Aj;
• i * log m è il contributo di tutti i Bj.
Il tempo totale richiesto è O(log n) con n processori su PRAM CREW.
Esistono anche algoritmi che riducono la complessità temporale a
O(log log n) portando quindi il costo totale a O(n log log n).
AA 2011-2012
Esempio di applicazione
dell’algoritmo di fusione
A = [ 4, 6, 7, 10, 12, 15, 18, 20 ]
B = [ 3, 9, 16, 17 ]
log m
log m
rango(9:A) = 3
rango(17:A) = 6
r = [ 0, 3, 6, 8 ]
B0 = [ 3, 9 ]
B1 = [ 16, 17 ]
A0 = [ 4, 6, 7 ] A1 = [ 10, 12, 15] A2 = [ 18, 20 ]
AA 2011-2012
Dettaglio del partizionamento
begin
P0: r [ 0 ] = 0
r [ m / log m ] = n
for i = 1 to m/log m -1 pardo
Pi:
r [ i ] = rango(B[ i * log m -1] : A)
for i = 0 to m/log m -1 pardo
Pi:
Bi = (B[ i * log m ], …, B[ (i+1) * log m -1])
Ai = (A[ r [ i ] ], …, A[ r [i+1] -1])
end
AA 2011-2012
Ordinamento per fusione
La versione parallela del ben noto algoritmo mergesort si può
descrivere tramite un albero binario completo di altezza logn che
contiene nelle foglie gli n elementi da ordinare. Al passo k-esimo
lavorano in parallelo tutti i processori assegnati ai nodi del livello*
logn-k ed ogni processore esegue la fusione dei valori presenti nei
suoi nodi figli. Ad ogni passo il tempo parallelo richiesto è
determinato dall’algoritmo di fusione utilizzato e il numero di passi
è pari all’altezza dell’albero.
Per quanto detto in precedenza, il miglior costo che si può avere
per questo algoritmo è pertanto pari a O(n logn loglog n), non
ottimo, anche se molto efficiente.
* si suppone che la radice sia a livello 0 e le foglie a livello logn
AA 2011-2012
Verso un ordinamento ottimo
Nel 1988 R. Cole presentò sul SIAM Journal Computing la prima versione del pipelined
merge sort (poi conosciuto come algoritmo di Cole) che riduceva il calcolo delle fusioni ad
ogni livello. Da allora varianti e raffinamenti dell’algoritmo si sono susseguiti in letteratura
fino a raggiungere un costo ottimo su PRAM EREW.
L’idea di Cole per eliminare il tempo dovuto all’operazione di fusione è nata dal constatare
che le operazioni di fusione dei sottovettori non è necessario compierle in un sol passo, dato
che per arrivare alla soluzione bisogna percorrere tutto l’albero, dalle foglie alla radice.
Il calcolo delle fusioni ad ogni singolo livello si può pertanto realizzare in un numero
costante di passi fondendo, ad ogni passo, opportuni valori scelti a campione.
Questi valori campione, di numero costante, richiedono tempo parallelo O(1) per essere
calcolati, confrontati ed inseriti provvisoriamente nel vettore soluzione parziale.
Il calcolo di nuovi campioni ad ogni livello garantisce la realizzazione alla radice del
vettore ordinato (ottenute per ripetute fusioni parziali) e garantendo un tempo parallelo
logaritmico per l’intero algoritmo di ordinamento.
AA 2011-2012