Matrix vermenigvuldiging op parallelle computers met

advertisement
Matrix vermenigvuldiging op parallelle computers met
gebruikmaking van BSP
Geert Mulders
[email protected]
Joost van Bruggen
[email protected]
13 januari 2005
Inhoudsopgave
1 Inleiding
2
2 2-Dimensionaal Algoritme
2.1 inleiding . . . . . . . . . . . . . . .
2.2 datadistributie . . . . . . . . . . .
2.3 communicatie van submatrices . .
2.4 vermenigvuldigen van submatrices
2.5 het algoritme . . . . . . . . . . . .
2.6 implementatie . . . . . . . . . . . .
2.7 kosten analyse . . . . . . . . . . .
2.7.1 communicatie . . . . . . . .
2.7.2 berekeningen . . . . . . . .
2.7.3 totale kosten . . . . . . . .
2.7.4 geheugen . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
3
4
4
4
5
5
6
6
6
6
3 3-Dimensionaal Algoritme
3.1 inleiding . . . . . . . . . . . . . . . . .
3.2 initiële datadistributie . . . . . . . . .
3.3 stap 1: datadistributie . . . . . . . . .
3.4 stap 2: submatrices vermenigvuldigen
3.5 stap 3: optellen van producten . . . .
3.6 in het algemeen . . . . . . . . . . . . .
3.7 algoritme . . . . . . . . . . . . . . . .
3.8 kosten analyse . . . . . . . . . . . . .
3.8.1 communicatie . . . . . . . . . .
3.8.2 berekenen . . . . . . . . . . . .
3.8.3 totale kosten . . . . . . . . . .
3.8.4 geheugen . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
7
7
7
7
7
8
8
9
9
9
9
10
10
4 Test resultaten
4.1 inleiding . . . . . . . .
4.2 het 2d programma . .
4.3 het 3d programma . .
4.4 sequentieel programma
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
11
11
11
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5 Conclusie
13
A Source code 2d programma
14
B Source code 3d programma
18
C Source code sequentiëel programma
21
1
Hoofdstuk 1
Inleiding
In dit verslag wordt een tweetal algoritmen besproken om 2 vierkante n × n matrices, aangeduid
met A en B, met elkaar te vermenigvuldigen. De resultaatmatrix noemen we C = AB. De
programma’s zijn geschreven in de programmeertaal C met gebruikmaking van BSP [1] en getest
op de supercomputer Teras van het Sara instituut. Dit project is een onderdeel van de cursus
Parallelle Algoritmen van de faculteit Wiskunde en Informatica aan de Universiteit Utrecht, die
gegeven wordt door de heer Bisseling [2].
De code die naar aanleiding van dit verslag is geschreven is te vinden op onze homepage [3] en
is als appendix opgenomen in dit verslag.
2
Hoofdstuk 2
2-Dimensionaal Algoritme
2.1
inleiding
In dit hoofdstuk wordt een zogenaamd 2-dimensionaal algoritme voor het vermenigvuldigen van
vierkante matrices op een parallelle computer besproken. De term 2-dimensionaal slaat op de
verdeling van de inputmatrices over de processoren en op de manier waarop de processoren
genummerd zijn.
2.2
datadistributie
Voor de datadistributie gebruiken we een blokdistributie; dat wil zeggen dat de n × n inputmatrices A en B in blokken ter grootte van bs = n/q over p = q 2 processoren worden verdeeld. We
nemen hierbij aan dat p een kwadraat is van een integer en dat n deelbaar is door q zodat de
blocksize bs op elke processor dezelfde is. Elke processor, die met een 2-dimensionaal processornummer P (s, t) wordt aangeduid, heeft dus 2 submatrices ter grootte bs × bs van respectievelijk
inputmatrix A en B in zijn geheugen. We duiden deze submatrices op processor P (s, t) aan met
Ast en Bst . Zie figuur 2.1 voor een grafische weergave van deze manier van distribueren. Voor
deze distributie is communicatie vereist omdat alleen processor P (0, 0) IO kan uitvoeren.
size
P(0,0)
bs
P(2,1)
Figuur 2.1: Blokdistributie van een size×size inputmatrix over 9 processoren. Op elke processor
P (s, t) wordt de grootte van de submatrices gegeven door bs × bs
In de opdracht mag er van uitgegaan worden dat elke processor de 2 bijbehorende submatrices
uit respectievelijk A en B heeft. In het door ons geschreven programma kan gebruik gemaakt
worden van tekst files (a.txt en b.txt) met daarin de 2 inputmatrices, die door processor
P (0, 0) worden ingelezen en vervolgens verdeeld over de andere processoren. Omdat dit nogal
wat tijd vergt, hebben we een optie toegevoegd die de submatrices op de verschillende processoren
3
volgens een simpel algoritme met data vult, zonder dat daarbij communicatie gebruikt wordt.
Er dient hierbij opgemerkt te worden dat de tekstfiles a.txt en b.txt wel aanwezig moeten zijn
om het programma te laten werken. Het programma zal als grootte van de met het algoritme
gegenereerde matrices het eerste getal uit de tekstfiles nemen.
2.3
communicatie van submatrices
Na de initiële datadistributie worden op elke processor die submatrices met elkaar vermenigvuldigd zodat op processor P (s, t) precies het goede blok van de resultaatmatrix, dat wil zeggen
Cst , berekend wordt. Hiervoor moet natuurlijk weer gecommuniceerd worden, omdat processor
P (s, t) de blokken van alle processoren die in dezelfde rij als P (s, t) zitten (dit geven we voortaan
aan met P (s, ∗)), nodig heeft, alsook de blokken van alle processoren in dezelfde kolom : P (∗, t).
2.4
vermenigvuldigen van submatrices
Heeft processor P (s, t) eenmaal 2 blokken (een blok van A en het bijbehorende blok van B),
dan kunnen die met elkaar vermenigvuldigd worden. Dan moeten de volgende 2 blokken van
de juiste processoren gehaald worden en hun product bij het vorige opgeteld. Deze stap wordt
zolang herhaald totdat alle blokken uit rij P (s, ∗) vermenigvuldigd zijn met de bijbehorende
blokken uit kolom P (∗, t).
De vermenigvuldiging van 2 submatrices [a] en [b] maakt gebruik van het volgende:
[c]ij =
bs−1
X
[a]ik [b]kj ,
k=0
wat gemakkelijk met behulp van 3 for loops geı̈mplementeerd kan worden.
2.5
het algoritme
Samenvattend komen we tot Algoritme 1, dat op elke processor hetzelfde uiterlijk heeft, maar
op andere data werkt (het zogenaamde Single Program Multiple Data principe).
Algoritme 1 2D Algoritme
1: p ← bsp_nprocs() {het aantal gebruikte processoren}
√
2: q ← p
3: s ← bsp_pid() {elke processor heeft een uniek nummer (0,1,..,p-1)}
4: a ← s div q {van 1D naar 2D processornummering (rij)}
5: b ← s mod q {van 1D naar 2D processornummering (kolom)}
6: initialiseer outputsubmatrix [c] met 0-en
7: for t = 0 to q − 1 do
8:
[a] ← [a](t+a∗q) {get submatrix [a] van andere processor (zelfde rij) }
9:
[b] ← [b](t∗q+b) {get submatrix [b] van andere processor (zelfde kolom)}
10:
[c] ← [c]oud + [a] · [b] {vermenigvuldig de submatrices en tel op}
11: end for
We zien hier duidelijk het communiceren van de submatrices optreden. Hierbij wordt expliciet
gebruik gemaakt van de 2-dimensionale processornummering, terwijl binnen de BSP bibliotheek
processoren met 1 getal worden aangeduid (een integer s in het bereik 0 ≤ s < p). De submatrices
[a] worden van elke processor in dezelfde rij als de uitvoerende processor gehaald, terwijl de
bijbehorende submatrices [b] van de processoren in dezelfde kolom worden gehaald. Daarna zien
we de vermenigvuldiging van de submatrices plaatsvinden. De · stelt dan ook het matrixproduct
voor en de gebruikte methode is de in sectie 2.4 genoemde.
4
2.6
implementatie
Nu gaan we in op de implementatie van het hiervoor beschreven algoritme.
Voordat 2 submatrices op een bepaalde processor met elkaar vermenigvuldigd kunnen worden,
moeten die lokaal aanwezig zijn. Hiervoor maken we gebruik van get operaties. Na de get
operaties wordt een synchronisatie gedaan, zodat het zeker is dat de benodigde data aanwezig
zijn. Zie code fragment 2.1 voor de details. De functies die beginnen met bsp zijn BSP specifieke
functies [2].
Om de communicatie gebalanceerd te houden, beginnen we op elke processor met het get-en van
de lokale submatrix [a] en de bijbehorende (in het algemeen niet lokale) submatrix [b]. Daarna
verkrijgen we door middel van een get-operatie de submatrix [a] van de processor die zich naast
de processor bevindt waarop het programma draait en de submatrix [b] van de processor die zich
onder de vorige bevindt. Zo wordt er doorgegaan totdat alle processoren uit de rij (en dus ook
alle processoren uit bijbehorende kolom) aan bod zijn geweest.
Listing 2.1: submatrices communiceren en vermenigvuldigen
int
int
int
int
s
p
q
bs
=
=
=
=
bsp pid ( ) ;
bsp nprocs ( ) ;
s q r t ( p + 0 . 5 ) ; // + 0 . 5 : a f r o n d i n g g a a t dan z e k e r goed
n/q ;
double ∗ ∗ matrixA
= m a t a l l o c d ( bs , bs ) ;
double ∗ pA
= matrixA [ 0 ] ;
double ∗ ∗ matrixAtemp = m a t a l l o c d ( bs , bs ) ;
double ∗ pAtemp
= matrixAtemp [ 0 ] ;
b s p p u s h r e g (pA
, bs ∗ bs ∗SZDBL ) ;
b s p p u s h r e g ( pAtemp , bs ∗ bs ∗SZDBL ) ;
// idem voor matrixB , pB , matrixBtemp , pBt
for ( t =0; t<q ; t++)
{
// 1D naar 2D processornummering
int a = s / q ;
// r i j nummer vd p r o c e s s o r
int b = s % q ;
// kolom nummer vd p r o c e s s o r
int c = ( b+t ) % q ; // b e s p a a r t r e k e n w e r k
// g e b a l a n c e e r d e communicatie van s u b m a t r i c e s
b s p g e t ( a∗q +
c , pA , 0 , pAt , bs ∗ bs ∗SZDBL ) ;
b s p g e t ( b + c ∗q , pB , 0 , pBt , bs ∗ bs ∗SZDBL ) ;
bsp sync ( ) ;
// s u b m a t r i c e s v e r m e n i g v u l d i g e n
for ( int i =0; i <bs ; i ++)
for ( int j =0; j <bs ; j ++)
for ( int k =0; k<bs ; k++)
matrixC [ i ] [ j ] += matrixAtemp [ i ] [ k ] ∗ matrixBtemp [ k ] [ j ] ;
}
bsp sync ( ) ;
2.7
kosten analyse
Om de kosten van het algoritme te bepalen, moeten we ons realiseren dat die uit 3 belangrijke
ingrediënten bestaan: communicatie, berekeningen en synchronisatie. We zullen gebruik maken
5
van de door Bisseling [2] voorgestelde kostenfuncties. De kosten om een h-relatie te communiceren, bedragen Tcomm (h) = hg + l waarbij g de kosten zijn om 1 datawoord te communiceren (in
flops) en l een parameter is die alle vaste kosten (in flops) in zich verzamelt. De kosten van een
rekenstap worden gegeven door Tcomp (w) = w + l, waarbij l dezelfde betekenis en waarde heeft
als bij de communicatie kosten en w het maximaal aantal flops in de computatie stap is (op 1
processor). Hiermee kan de kostenfunctie van elk BSP algoritme geschreven worden als:
T = a + bg + cl.
(2.1)
Een BSP computer kunnen we karakteriseren door 4 parameters: p, r, g, en l. Hierbij staat p
voor het aantal processoren en r voor de snelheid van 1 processor (in flop/s). Nu kunnen we een
schatting maken van de benodigde tijd om het programma uit te voeren (mits we de waarden
van de parameters van de gebruikte machine kennen, bijv. door benchmarking); deze is namelijk
t = T /r.
Verder speelt de gebruikte hoeveelheid geheugen een rol; deze zullen we ook analyseren.
2.7.1
communicatie
Elke processor vraagt q keer 2 submatrices van steeds een andere processor. Die submatrices
hebben een grootte van n/q × n/q = n2 /p. Aangezien elke processor op hetzelfde moment zulke
datablokken opvraagt en als eerste altijd de lokale submatrix a en bijbehorende submatrix b
(die in het algemeen niet lokaal is) ontvangt, verzendt elke processor steeds evenveel als hij
ontvangt. We kunnen dus spreken van een volle h-relatie, met h = 2n2 /p in het geval dat er
geen lokale submatrix (op alle processoren!) bij betrokken is. De allereerste communicatiestap
is echter zeker goedkoper, omdat de submatrix [a] niet gecommuniceerd hoeft te worden; dan
geldt h = n2 /p. De totale communicatie kosten komen hiermee op:
2
n2
n
n2
n2
+ l) + (g ×
+ l) = 2 √ −
Tcomm = (q − 1) × (g × 2
g + ql.
p
p
p
p
2.7.2
berekeningen
Voor het product van 2 submatrices worden 2(bs)3 berekeningen gedaan, waarbij de (bs)3 komt
van de 3 gebruikte for loops en de factor 2 omdat er steeds én een optelling én een vermenigvuldiging wordt uitgevoerd. In totaal worden er op elke processor q keer 2 submatrices
vermenigvuldigd, zodat we op een totaal uitkomen van:
Tcomp = q × (2(bs)3 + l) = 2
2.7.3
n3
+ ql.
p
totale kosten
De totale kosten worden met behulp van de eerder voorgestelde kostenfunctie (2.1) gegeven door:
2
n3
n
n2
T = Tcomm + Tcomp = 2
g + 2ql.
+ 2√ −
p
p
p
2.7.4
geheugen
Op elke processor worden 5 matrices gedeclareerd ter grootte bs × bs. Bij elkaar hebben we dus
5 × n × n × SZDBL geheugen nodig. Bovendien worden op processor P (0, 0) de 2 inputmatrices
A en B ingelezen, waarvoor 2 × n × n × SZDBL geheugen nodig is. De parameter SZDBL =
sizeof(double) geeft de grootte van een double in bytes.
Er wordt gebruik gemaakt van tijdelijke matrices (zie code fragment 2.1) omdat de lokale matrices
in het algemeen nog nodig zijn op andere processoren en dus niet overschreven dienen te worden.
6
Hoofdstuk 3
3-Dimensionaal Algoritme
3.1
inleiding
In dit hoofdstuk wordt de 3-dimensionale variant van het hiervoor besproken algoritme onderzocht. De term 3-dimensionaal slaat weer op de verdeling van de inputmatrices over de
√
processoren en op de manier waarop de processoren genummerd zijn. In dit geval geldt q = 3 p.
3.2
initiële datadistributie
Zoals aangegeven in de opdracht, mocht er van uitgegaan worden dat de matrices over de processoren zijn verdeeld. We hebben er dan ook voor gekozen om het programma volgens een simpel
algoritme getallen in de submatrices op de verschillende processoren te laten genereren en het
daadwerkelijke verdelen achterwegen te laten.
De submatrix Asu van inputmatrix A is bij aanvang van het algoritme gedistribueerd over de
rij van processoren met nummers P (s, ∗, u), terwijl de submatrix But van inputmatrix B gedistribueerd is over de kolom van processoren met nummers P (∗, t, u). Dit betekent dus dat beide
inputmatrices in q 2 submatrices ter grootte n/q ×n/q zijn verdeeld. Elke submatrix is bovendien
over zijn bijbehorende kolom of rij gedistribueerd, wat inhoudt dat elke processor in die rij of
die kolom een rij of kolom krijgt uit de submatrix. Zie figuur 3.1 voor een expliciet voorbeeld.
√
De grootte van de vierkante blokken is bs = n/q. Hierbij heeft q de waarde 3 p. We nemen aan
dat p de derde macht is van een integer en dat n een veelvoud is van q, zodat de blocksize op
elke processor dezelfde waarde heeft.
3.3
stap 1: datadistributie
In de eerste stap van het algoritme wordt door elke processor de data die lokaal aanwezig zijn,
verzonden naar die processoren die de data later in het algoritme nodig hebben. Voor data uit
inputmatrix A zijn dit de processoren in dezelfde rij als de versturende processor en voor de data
uit B de processoren in dezelfde kolom (zie figuur 3.2). Hierbij moet er op gelet worden dat de
data in de goede volgorde in de lokale submatrices terecht komen.
3.4
stap 2: submatrices vermenigvuldigen
Na de datadistributie worden op elke processor de 2 aanwezige submatrices met elkaar vermenigvuldigd (op de manier zoals beschreven in sectie 2.4). Hiervoor hoeft dus geen communicatie
plaats te vinden.
7
size
a00
a01
a02
a03
a10
a11
a12
a13
bs
z
p=8
q=2
y
a20
[a]10
a21
a22
a23
a30
a31
a32
a33
b00
b01
b02
b03
b10
b11
b12
b13
b20
b21
b22
b23
x
b00 b01
P001
P011
P101
P111
[b]00
b30
b31
b32
b33
P010
P000
[b]00
b10 b11
a20
a30
P100
a21
a31
z=1
P110
z=0
[a]10
Figuur 3.1: De initiële verdeling van twee submatrices van de twee 4 × 4 inputmatrices A en B
over 8 processoren
Figuur 3.2: De datadistributie in een processorkolom
3.5
stap 3: optellen van producten
De derde en laatste stap is het optellen van de matrixproducten. We zorgen er voor dat de
resultaatmatrix C op een zelfde manier als de inputmatrix B verdeeld is over de processoren.
We doen dit om er voor te zorgen dat er zo min mogelijk gecommuniceerd hoeft te worden.
Zo komt in het voorbeeld van figuur 3.1 de bovenste rij van de som van de 2 submatrices die
het resultaat zijn van de lokale vermenigvuldiging op processoren P (1, 0, 0) en P (1, 0, 1) terecht
op P (1, 0, 0) terwijl de onderste rij op P (1, 0, 1) terecht komt. Op deze manier hoeven niet hele
submatrices gecommuniceerd te worden; het volstaat om voor de processor P (x, y, i) in het i-de
z-vlak de i-de rij van elk van de submatrices op de processoren met dezelfde x en y op te halen.
3.6
in het algemeen
In het algemeen is het zo dat er niet 1 rij of 1 kolom gecommuniceerd wordt, maar dat er sprake
is van een blok van aanliggende rijen of kolommen. Er zullen in het algemeen namelijk meer
kolommen (of rijen) in een submatrix zijn dan er processoren zijn. Het komt er op neer dat
bs/q > 1. Deze verzamelingen rijen of kolommen noemen we een blok ; een blok heeft een breedte
van bs/q en elke submatrix wordt in q van die blokken verdeeld.
8
3.7
algoritme
Samenvattend komen we tot Algoritme 2, dat op elke processor weer hetzelfde uiterlijk heeft,
maar op andere data werkt (het zogenaamde Single Program Multiple Data principe).
Algoritme 2 3D Algoritme
1: p ← bsp_nprocs() {het aantal gebruikte processoren}
√
2: q ← 3 p
3: s ← bsp_pid() {elke processor heeft een uniek nummer (0,1,..,p-1)}
4: x ← s mod (q ∗ q) mod q {van 1D naar 3D processornummering}
5: y ← s mod (q ∗ q) div q
6: z ← s div (q ∗ q)
{STAP 1: datadistributie, zie figuur 3.2}
7: op processor P (i, j, k)
8: for all x 6= i,y 6= j do
9:
get rij x van [a] op processor P (x, j, k) en stop die in rij x van de lokale [a]
10:
get kolom y van [b] op processor P (i, y, k) en stop die in kolom y van de lokale [b]
11: end for
{STAP 2: submatrices vermenigvuldigen}
12: [c] ← [a] · [b]
{STAP 3: optellen van resultaten}
13: op processor P (i, j, k)
14: for all z 6= k do
15:
get rij i van [c] op processor P (i, j, z) en en tel die op bij rij i van de lokale [c]
16: end for
3.8
kosten analyse
We maken weer gebruik van de theoretische relaties die in het BSP model gelden en die al in
sectie 2.7 zijn besproken.
3.8.1
communicatie
In de stappen 1 en 3 van het algoritme vindt communicatie plaats.
In stap 1 zendt elke processor een kolom van de lokale submatrix [a] en een rij van de lokale
submatrix [b] naar de q 1 processoren in dezelfde processor-rij respectievelijk dezelfde processorkolom. Tegelijkertijd worden ook zulke kolommen en rijen ontvangen.
De grootte van zo’n kolom (of rij) is 1/q × (bs)2 = n2 /p. Aangezien er naar q processoren wordt
gezonden, en elke processor gelijktijdig met de andere bezig is, hebben we een h-relatie met
h = 2n2 /p2/3 .
In stap 3 worden de resultaten weer in de bekende verdeling gebracht. Hiervoor geldt h =
n2 /p2/3 , omdat er nu slechts steeds 1 rij hoeft te worden gecommuniceerd (uit de lokale resultaatsubmatrix).
De totale communicatie kosten komen hiermee op:
Tcomm = 3g
3.8.2
n2
+ ql.
p2/3
berekenen
Berekenen vindt plaats in stappen 2 en 3.
1 Eigenlijk wordt er naar q − 1 processoren gezonden. Door gebruik te maken van q zijn de uiteindelijke
uitdrukkingen van een mooiere vorm.
9
In stap 2 worden de submatrices vermenigvuldigd. Dit levert de kosten Tcomp,2 = 2 × (bs)3 =
2n3 /p op.
In stap 3 worden q keer 2 rijen ter grootte n2 /p opgeteld, wat de volgende kosten met zich
meebrengt: Tcomp,3 = q × n2 /p = n2 /p2/3 .
De totale reken kosten worden gegeven door:
Tcomp =
3.8.3
n2
2n3
+ 2/3 + ql.
p
p
totale kosten
De totale kosten van het 3-dimensionale algoritme komen met gebruikmaking van de eerder
voorgestelde kostenfunctie (2.1) en het voorgaande op:
T = Tcomp + Tcomm = 2
3.8.4
n3
n2
n2
+ 2/3 + 3 2/3 g + 2p1/3 l.
p
p
p
geheugen
Op elke processor worden 4 submatrices ter grootte bs × bs gedeclareerd. We vinden een van het
aantal processoren afhankelijk geheugengebruik van 4 p3/2 × n2 × SZDBL.
10
Hoofdstuk 4
Test resultaten
4.1
inleiding
In dit hoofdstuk wordt besproken hoe de twee verschillende implementatie’s presteren. Hiervoor
zijn de 2 geschreven programma’s gedraaid op de parallelle computer Teras van SARA Rekenen Netwerkdiensten [4] met matrices van verschillende grootte als input en met gebruikmaking
van verschillende aantallen processoren.
4.2
het 2d programma
Om te kijken hoe het programma schaalt met het aantal gebruikte processoren, hebben we een
aantal runs uitgevoerd. Zie tabel 4.1 voor de resultaten.
size \ p
100
300
360
600
720
1080
1440
1800
1
0.11
2.84
24.2
38.5
-
4
0.04
0.87
1.33
6.03
9.84
33.5
89.4
195.6
9
0.40
0.70
2.90
4.62
17.6
40.8
80.7
Tabel 4.1: Resultaten verkregen met het 2d programma; de aangegeven tijden zijn in seconden.
Bij gebruik van dit programma is het voordeel van het parallelliseren duidelijk zichtbaar.
4.3
het 3d programma
Ook voor het programma dat gebruik maakt van het 3-dimensionale algoritme, hebben we gekeken hoe het schaalt met het aantal gebruikte processoren. Zie tabel 4.2 voor de resultaten.
4.4
sequentieel programma
Ter vergelijking is nog een sequentiële implementatie gemaakt van het algoritme om matrices te
vermenigvuldigen. Zoals verwacht is dit programma iets sneller dan de parallelle versies als die
gebruik maken van slechts 1 processor. Zie tabel 4.3
Zie de appendix voor de code van het sequentiële programma.
11
size \ p
100
200
300
400
500
600
700
800
900
1000
1500
2000
1
0.10
0.74
2.31
6.12
11.1
23.5
38.3
61.6
93.9
134.0
-
8
0.22
0.85
2.00
3.63
5.66
9.06
12.5
17.8
21.7
29.4
89.6
241.8
Tabel 4.2: Resultaten verkregen met het 3d programma; de aangegeven tijden zijn in seconden.
size \ p
100
300
500
700
900
1000
1
0.08
2.19
10.8
36.4
88.1
131.1
Tabel 4.3: Resultaten verkregen met het sequentiële programma; de aangegeven tijden zijn in
seconden.
12
Hoofdstuk 5
Conclusie
Bij gebruik van meerdere processoren zijn de geschreven programma’s sneller dan een standaard
programma dat geen gebruik gemaakt van parallellisme. Het programma dat gebruik maakt
van het 3-dimensionale algoritme is bij de gedane test-runs niet sneller dan het programma dat
gebruik maakt van het 2-dimensionale algoritme. Het is echter wel zo dat er een trend zichtbaar is
die laat zien dat het 3-dimensionale algoritme bij gebruik van grote matrices en veel processoren,
zoals ook uit het vergelijken van de theoretische kostenfunctie’s te zien is, voordeliger zal zijn.
13
Bijlage A
Source code 2d programma
/************************************************************************************
* parallel program for computing the product of two square N x N - matrices A and B *
* it can make use of p=1,4,9,16,... processors (with a maximum of N x N processors) *
* and the restriction N mod sqrt(p) = 0 has to be satisfied
*
* the two input matrices are read from the files a.txt and b.txt
*
* in these files, the first number indicates the value of N
*
*************************************************************************************/
#include "bspedupack.h"
int P;
double** A;
double** B;
int size;
/*
/*
/*
/*
number of processors requested */
input matrix A */
input matrix B */
size (in one dimension) of input matrices */
/******************
* SPMD FUNCTION *
*******************/
void matrixm()
{
int p;
/* number of processors being used */
int s;
/* current processor number */
int q;
/* sqrt of p; number of blocks on a row (or in a column) */
int bs;
/* blocksize: size of submatrices (which are bs x bs) */
int i, j, k, t; /* counters */
int a, b;
/* matrices */
double** matrixA;
double** matrixB;
double** matrixC;
double** matrixAtemp;
double** matrixBtemp;
/* pointers to these matrices used for get operations */
double* pA;
double* pB;
double* pAt;
double* pBt;
/* timers */
double time0, time1;
/* file pointer */
//
FILE * file;
/* BEGIN PARALLEL PART */
bsp_begin(P);
p = bsp_nprocs();
s = bsp_pid();
q = (int) (sqrt(p)+0.5);
/* CHECKS */
if(q*q != p)
bsp_abort("Error: wrong value of q!");
if (size*size < p) /* blocksize will be smaller than 1 */
{
if (s==0)
printf("Error: too many processors requested. Blocksize will be smaller than 1.\n");
exit(1);
}
14
if (size%q == 0)
bs = size/q;
else
/* blocksize will differ among processors */
{
if (s == 0)
printf("Error: matrix dimensions incompatible with number of processors.\n");
exit(1);
}
/* DATA DISTRIBUTION */
/* allocate matrices */
matrixA
= matallocd(bs, bs);
matrixB
= matallocd(bs, bs);
matrixC
= matallocd(bs, bs);
matrixAtemp = matallocd(bs, bs);
matrixBtemp = matallocd(bs, bs);
/* set pointers */
pA = matrixA[0];
pB = matrixB[0];
pAt = matrixAtemp[0];
pBt = matrixBtemp[0];
/* register pointers */
bsp_push_reg(pA, bs*bs*SZDBL);
bsp_push_reg(pB, bs*bs*SZDBL);
bsp_push_reg(pAt, bs*bs*SZDBL);
bsp_push_reg(pBt, bs*bs*SZDBL);
bsp_sync();
time0 = bsp_time();
//
//
//
//
//
//
//
/* DISTRIBUTE the data */
if (s == 0)
for (i = 0; i < size; i++)
for (j = 0; j < size; j++)
{
bsp_put( ((i/bs)*q + j/bs)%p, &A[i][j], pA, (i*bs + j%bs) % (bs*bs) * SZDBL, SZDBL);
bsp_put( ((i/bs)*q + j/bs)%p, &B[i][j], pB, (i*bs + j%bs) % (bs*bs) * SZDBL, SZDBL);
}
/* submatrix generation for testing purposes (faster) */
for (i = 0; i < bs; i++)
for (j = 0; j < bs; j++)
{
a = i+j;
matrixA[i][j] = a;
matrixB[i][j] = a;
}
bsp_sync();
time1 = bsp_time();
//
//
//
//
//
if (s == 0)
{
printf("Time required for data distribution: %.12lf seconds.\n", time1 - time0);
fflush(stdout);
}
/* COMPUTATION */
/************************************************************************************************
* get blocks from other processors and put those blocks locally in matrixAtemp and matrixBtemp *
* the A-blocks come from all processors in the same processor row (2D processor numbering)
*
* the B-blocks come from all processors in the same processor column (2D processor numbering) *
************************************************************************************************/
/* initialize output matrix */
for (i = 0; i < bs; i++)
for (j = 0; j < bs; j++)
matrixC[i][j] = 0;
/* convert 1D numbering to 2D */
a = s / q;
b = s % q;
/* communicate and compute */
for(t = 0; t < q; t++)
{
/* get blocks (initialize the transfer) */
bsp_get( t + a*q, pA, 0, pAt, bs*bs*SZDBL);
bsp_get( t*q + b, pB, 0, pBt, bs*bs*SZDBL);
bsp_sync();
/* actually GET the blocks (do the transfer) */
/* calculate product of two blocks */
15
for (i = 0; i < bs; i++)
for (j = 0; j < bs; j++)
for (k = 0; k < bs; k++)
matrixC[i][j] += matrixAtemp[i][k] * matrixBtemp[k][j];
}
bsp_sync();
time0 = bsp_time();
if (s == 0)
{
printf("Time required for computation: %lf seconds.\n", time0-time1);
fflush(stdout);
}
//
//
//
/* RESULT MATRIX */
for (i = 0; i < bs; i++)
for (j = 0; j < bs; j++)
printf("%lf\t", matrixC[i][j]);
/* DEREGISTRATION and DEALLOCATION */
bsp_pop_reg(pA);
bsp_pop_reg(pB);
bsp_pop_reg(pAt);
bsp_pop_reg(pBt);
matfreed(matrixA);
matfreed(matrixB);
matfreed(matrixC);
matfreed(matrixAtemp);
matfreed(matrixBtemp);
bsp_end();
}
/*************************************************************************************************************************
*
reads a matrix from a file, stores the entries in memory and returns a pointer to that memory
*
*
the first number in the file must be an int, representing the size of one dimension of the square matrix
*
*
the following numbers can be doubles and are the entries of the matrix in order (row1 column1) (row1 column2) etc.
*
**************************************************************************************************************************/
double** readMatrix(char* filename, int* size)
{
int i, j;
double** matrix;
FILE* matrixfile;
/* open file */
if ((matrixfile = fopen(filename, "r")) == 0)
{
printf("Error: file does not exist.\n");
exit(1);
}
/* allocate memory */
fscanf(matrixfile, "%d", size);
matrix = matallocd(*size, *size);
/* write file to memory */
for (i = 0; i < *size; i++)
for (j = 0; j < *size; j++)
fscanf(matrixfile, "%lf", &matrix[i][j]);
return matrix;
}
/*************************************************************************
*
prints the entries of a two-dimensional memory block in matrix form *
**************************************************************************/
void printMatrix(double** matrix, int size)
{
int i, j;
if (size == 0)
printf("Error: the matrix size is zero.\n");
/* actually print */
for (i = 0; i < size; i++)
{
for (j = 0; j < size; j++)
printf("%lf\t", matrix[i][j]);
printf("\n");
}
printf("\n");
16
}
/************
* MAIN
*
*************/
int main(int argc, char** argv)
{
//
int sizeA, sizeB;
int Q;
/* initialize bsp */
bsp_init(matrixm, argc, argv);
//
//
//
//
//
//
//
//
//
/* sequential part */
A = readMatrix("a.txt", &sizeA);
B = readMatrix("b.txt", &sizeB);
if (sizeA != sizeB)
{
printf("Error: incompatible matrix dimensions.\n");
exit(1);
}
else
size = sizeA;
/* size of matrices */
printf("Give matrix size:");
fflush(stdout);
scanf("%d", &size);
/* number of processors to use */
printf("How many processors do you want to use?\n");
fflush(stdout);
scanf("%d", &P);
/* P has to have a value equal to 1,4,9,16,... */
Q = (int)sqrt(P);
if ( Q*Q - P != 0 )
{
P = Q*Q;
printf("Warning: using %d processors.\n", Q*Q);
}
/* P cannot be bigger than number of available processors */
if ( P > bsp_nprocs() )
{
printf("Error: not enough processors available.\n");
exit(1);
}
/* SPMD part */
matrixm();
exit(0);
}
17
Bijlage B
Source code 3d programma
#include "bspedupack.h"
/* This is a program that can be used for computing matrix-matrix multiplications in a 3D fashion
Therefore it will only run on 1,8,27,64,... processors */
int P;
int size;
/* number of processors requested */
/* size (in one dimension) of input matrices */
/* these functions are usefull when transforming processornumbers from 1D to 3D and vice versa
the offset function transforms a 2D matrix index in a memory position (1D) */
int pid(int q, int x, int y, int z)
{
return ((q*q*z) + (q*y) + x);
}
int xpid(int q, int s)
{
return (s%q);
}
int ypid(int q, int s)
{
return ((s/q)%q);
}
int zpid(int q, int s)
{
return (s/(q*q));
}
int offset(int bs, int x, int y)
{
return ((x + bs*y)*SZDBL);
}
/******************
* SPMD FUNCTION *
*******************/
void matrixm()
{
int p;
/* number of processors being used */
int s;
/* current processor number 1D */
int q;
/* q^3 = p; number of blocks on a row (or in a column) */
int bs;
/* blocksize: size of submatrices (which are bs x bs) */
int b;
/* b = bs/q */
int x, y, z;
/* current processor numbers 3D */
int i, j, k;
/* counters */
double t0, t1; /* timers */
/* matrices */
double ** A, ** B, ** C, ** D;
double * pA, * pB, * pC;
/* BEGIN PARALLEL PART */
bsp_begin(P);
p = bsp_nprocs(); s = bsp_pid(); q = (int) (pow(p, 0.333)+0.5);
x = xpid(q,s); y = ypid(q,s); z = zpid(q,s);
/* checks */
if(q*q*q != p) /* rounding errors? */
bsp_abort("Error: q has wrong value!");
if(size%q == 0)
18
{
bs = size/q;
b = bs/q;
}
else
/* blocksize will differ among processors */
{
if (s == 0)
printf("Error: matrix dimensions incompatible with number of processors.\n");
exit(1);
}
/* allocation, registration and initialization of matrices */
A = matallocd(bs, bs); B = matallocd(bs, bs); C = matallocd(bs, bs); D = matallocd(bs, bs);
pA = A[0]; pB = B[0]; pC = C[0];
bsp_push_reg(pA, bs*bs*SZDBL); bsp_push_reg(pB, bs*bs*SZDBL); bsp_push_reg(pC, bs*bs*SZDBL);
for(i = 0; i < bs; i++)
for(j = 0; j < bs; j++)
{
A[i][j] = 0; B[i][j] = 0; C[i][j] = 0; D[i][j] = 0;
}
/* STEP 0: initial distribution */
for(i = 0; i < bs; i++)
for(j = 0; j < b; j++)
{
A[i][j+y*b] = s;
B[j+x*b][i] = 2*s;
}
bsp_sync();
t0 = bsp_time();
/* STEP 1: distribution of elements */
for(i = 0; i < q; i++)
/* loop over processors
for(k = 0; k < b; k++)
/* loop over rows/columns in blocks in submatrices
for(j = 0; j < bs; j++) /* loop over elements in row/column of blocks of submatrices
{
if(i != y) /* skip local get */
bsp_get( pid(q,x,i,z), pA, offset(bs,i*b+k,j), &A[j][i*b+k], SZDBL);
/* get from all processors with same x and y number
get from their submatrices A
get a block of possibly more than one row (depends on b) and put it in local
if(i != x)
bsp_get( pid(q,i,y,z), pB, offset(bs,j,i*b+k), &B[i*b+k][j], SZDBL);
}
bsp_sync();
/* STEP 2: multiplication of local submatrices */
for(i = 0; i < bs; i++)
for(j = 0; j < bs; j++)
for(k = 0; k < bs; k++)
C[i][j] += A[i][k]*B[k][j];
/* STEP 3: add up z-planes; first communicate and than sum up */
/* STEP 3a: communicate */
for(i = 0; i < q; i++)
// loop over processors in z-direction
for(j = 0; j < bs; j++)
// loop over elements
for(k = 0; k < b; k++) // loop over rows/columns in a block
bsp_get( pid(q,x,y,i), pC, offset(bs,j,z*b+k), &B[i*b+k][j], SZDBL);
// get from processors with same x and y number
// get from C submatrix
// save in B submatrix (q*b x bs = bs x bs; so it will fit)
bsp_sync();
/* STEP 3b: sum up locally */
for(i = 0; i < q; i++)
for(k = 0; k < b; k++)
for(j = 0; j < bs; j++)
D[k+x*b][j] += B[i*b+k][j];
bsp_sync();
t1 = bsp_time();
/* test: result */
if (s == 0)
printf("Time needed: %lf\n", t1 - t0);
19
*/
*/
*/
submatrix A */
// for(i = 0; i < bs; i++)
//
for(j = 0; j < bs; j++)
//
printf("3 s = %d: D[%d][%d] = %f\n", s, i, j, D[i][j]);
bsp_end();
}
/************
* MAIN
*
*************/
int main(int argc, char** argv)
{
int Q;
/* initialize bsp */
bsp_init(matrixm, argc, argv);
/* size of matrices */
printf("Give matrix size.\n");
fflush(stdout);
scanf("%d", &size);
/* number of processors to use */
printf("How many processors do you want to use?\n");
fflush(stdout);
scanf("%d", &P);
/* P has to have a value equal to 1,8,27,64,... */
Q = (int)(pow(P, 0.333) + 0.5);
if ( Q*Q*Q - P != 0 )
{
P = Q*Q*Q;
printf("Warning: using %d processors.\n", Q*Q*Q);
}
/* P cannot be bigger than number of available processors */
if ( P > bsp_nprocs() )
{
printf("Error: not enough processors available.\n");
exit(1);
}
/* SPMD part */
matrixm();
exit(0);
}
20
Bijlage C
Source code sequentiëel
programma
#include <time.h>
#include <stdio.h>
double **matallocd(int m, int n)
{
/* This function allocates an m x n matrix of doubles */
int i;
double *pd, **ppd;
if (m==0)
{
ppd= NULL;
} else
{
ppd= (double **)malloc(m*sizeof(double *));
if (ppd==NULL)
{
printf("matallocd: not enough memory");
exit(1);
}
if (n==0)
{
for (i=0; i<m; i++)
ppd[i]= NULL;
} else
{
pd= (double *)malloc(m*n*sizeof(double));
if (pd==NULL)
{
printf("matallocd: not enough memory");
}
ppd[0]=pd;
for (i=1; i<m; i++)
ppd[i]= ppd[i-1]+n;
}
}
return ppd;
} /* end matallocd */
int main()
{
int i, j, k, size;
double ** A, ** B, ** C, time;
clock_t t0, t1;
printf("Gimme size: ");
fflush(stdout);
scanf("%d", &size);
A = matallocd(size, size);
B = matallocd(size, size);
C = matallocd(size, size);
for(i = 0; i < size; i++)
for(j = 0; j < size; j++)
21
{
A[i][j] = i*j;
B[i][j] = i*j;
}
t0 = clock();
for(i =
for(j =
for(k =
C[i][j]
0;
0;
0;
+=
i < size; i++)
j < size; j++)
k < size; k++)
A[i][k]*B[k][j];
t1 = clock();
time = (double)((double)t1 - (double)t0)/(double)CLOCKS_PER_SEC;
printf("\n\nTicks: %d, CLK_TCK: %d, Total time: %lf\n", t1-t0, CLOCKS_PER_SEC, time);
return 0;
}
22
Bibliografie
[1] http://www.bsp-worldwide.org/
[2] Rob H. Bisseling, Parallel Scientific Computation, Oxford University Press, 2004
[3] http://www.phys.uu.nl/~vbrug/vakken/wipa/project1
[4] http://www.sara.nl
23
Download