</motto<T{m{ tarina kulkee meid{n p{ittemme yli!

                            -- -- Sokal: Usvah{{t
>>

/chapter</ii<Rinnakkaislaskenta>>

  
Toinen tapa vektoroinnin lis{ksi nopeuttaa laskentaa on
jakaa ty| useammalle prosessorille.  ''Perinteisiss{''
tietokoneissa prosessoreita on vain muutamia,
tyypillisesti 1--8 kappaletta ja ne pystyv{t kukin
suorittamaan itsen{isesti omaa ohjelmaansa. 
Viime aikoina on ruvettu tutkimaan my|s massiivisesti
rinnakkaisia laitteistoja, jotka koostuvat hyvin
suuresta m{{r{st{ suhteellisen yksinkertaisia
prosessoreita.  Seuraavassa emme puutu t{m{ntyyppiseen
rinnakkaislaskentaan.
 
Rinnakkaistettu ohjelma on huomattavasti monimutkaisempi
kuin vastaava yksiprosessoriversio johtuen mm.  ohjelman eri
osien synkronoinnista, usean prosessorin k{yt|ss{ olevien
yhteisten muuttujien suojaamisesta ja muusta kirjanpidosta. 
T{m{n vuoksi rinnakkaistettu ohjelma vie aina enemm{n
CPU-aikaa (kun lasketaan yhteen kaikkien prosessoreiden
k{ytt{m{ aika) kuin yhdell{ prosessorilla ajettava versio. 
Hy|tyn{ on ty|n nopeampi /ii<l{pimenoaika> ja mahdollisesti my|s
laitteiston parantunut suorituskyky. 

Useisiin rinnakkaislaskennan ongelmiin t|rm{t{{n my|s
kaikissa sellaisissa yksiprosessorikoneissa, joissa
samanaikaisesti voi olla k{ynniss{ useita t|it{.  Vaikka
prosessori suorittaakin tietyll{ hetkell{ vain yht{ ty|t{,
voi koneessa olla useita eri vaiheissa olevia k{ytt{jien
t|it{ ja k{ytt|j{rjestelm{n prosesseja, joiden pit{{
jotenkin pysty{ kommunikoimaan kesken{{n ja synkronoimaan
toimintansa. Jonkinasteista rinnakkaisk{sittely{ tapahtuu
mikrotietokoneissakin, jotka pystyv{t suorittamaan 
laskentaa ja I//O-toimenpiteit{ samanaikaisesti.

Rinnakkaistettu ty| (task) jakaantuu useammaksi prosessiksi.
Yksi prosessi k{ytt{{ aina yht{ prosessoria, mutta yleens{
sille ei varata mit{{n tietty{ prosessoria. Kuormitetussa
koneessa prosessi voi v{lill{ joutua odottamaan vapaata
prosessoria, ja p{{stess{{n suoritettavaksi k{ytt|j{rjestelm{
voi antaa sille mink{ tahansa vapaana olevista prosessoreista.

 
/section<Rinnakkaistamisen tyypit>
 
Moniprosessorikoneissa ohjelma voi k{ytt{{ useita
prosessoreita, mik{li siin{ on toisistaan riippumattomia
palasia, jotka saa suorittaa samanaikaisesti.

</sl/ii<Makrotason rinnakkaistamisessa>> (/ii<macrotasking>)
suoritetaan useita aliohjelmia samaaikaisesti.  
T{llainen rinnakkaistaminen edellytt{{ yleens{,
ett{ ohjelmoija huolehtii itse rinnakkain suoritettavien
ohjelman osien synkronoinnista. 

K{ytt|j{rjestelm{t tekev{t t{m{ntyyppist{ rinnakkaisk{sittely{
k{ytt{j{n sit{ oikeastaan huomaamatta.  Esimerkiksi </sc unix>in 
/ii<putken> p{iss{ olevat prosessit saattavat toimia
samanaikaisesti eri prosessoreissa.  My|s
yksiprosessorisissa moniajokoneissa toimii joukko toisistaan
riippumattomia prosesseja.  Siksi useimmista
k{ytt|j{rjestelmist{ l|ytyy jonkinlaisia ty|kaluja
rinnakkaisten prosessien hallintaan. 

</sl/ii<Mikrotason rinnakkaistaminen>> (/ii<microtasking>)
on nimenomaan moniprosessorikoneille sopiva tapa jakaa
yksitt{isen ohjelman aiheuttamaa kuormaa. Tyypillisesti
yksitt{iset silmukat jaetaan usealle prosessorille. 
T{llaista rinnakkaistamista er{{t k{{nt{j{t
(kuten Crayn </tt cf77>) pystyv{t tekem{{n
automaattisesti. 
 

/section<Muuttujien tilavaraus>

/iii<lokaali muuttuja>
/iii<paikallinen muuttuja>
/iii<globaali muuttuja>
Ohjelmissa on tavallisesti kahdentyyppisi{ muuttujia: 
</sl lokaaleja> eli </sl paikallisia> ja </sl globaaleja>. 
Paikallisiin muuttujiin voidaan viitata vain siin{
aliohjelmassa, jossa ne on m{{ritelty.  Eri aliohjelmissa
voi olla samannimisi{ muuttujia, koska kukin aliohjelma
tuntee niist{ vain omansa.  Globaalit muuttujat ovat
useampien aliohjelmien tiedossa.  </sc Fortran>issa
globaaleja muuttujia ovat </tt common>-alueille talletetut
muuttujat. 

/iii<yksityinen muuttuja> /iii<private variable>
/iii<jaettu muuttuja> /iii<shared variable>
Moniprosessoinnissa muuttujat jaetaan lis{ksi kahteen
luokkaan sen mukaan, ovatko ne yhden ainoan prosessin
k{ytett{viss{, vai voiko useampi prosessi k{ytt{{ niit{
samanaikaisesti.  Yhden prosessin omistamat muuttujat ovat
</sl yksityisi{> (private).  Jos muuttuja on usean prosessin
yhteinen, se on </sl jaettu> (shared).  Sek{ paikalliset ett{
globaalit muuttujat voivat olla yksityisi{ tai jaettuja. 

</bf Yksityiset paikalliset> muuttujat luodaan aina
uudestaan kutsuttaessa aliohjelmaa. Koska yksik{{n toinen
aliohjelma tai prosessi ei voi viitata niihin, ne eiv{t
aiheuta ongelmia rinnakkaistamiselle. T{llaiset muuttujat
eiv{t esiinny </tt data>, </tt save>, </tt common> tai
</tt task common> -lauseissa.

</bf Yksityiset globaalit> muuttujat esiintyv{t 
</tt/ii<task common>> -lauseessa. Ne varataan pinosta,
ovat kaikkien samaan prosessiin kuuluvien aliohjelmien
k{ytett{viss{, ja ne h{vi{v{t rinnakkaisen osan
p{{ttyess{. Koska vain yksi prosessori voi viitata
n{ihin muuttujiin, nek{{n eiv{t aiheuta suojausongelmia.

</bf Jaetut paikalliset> muuttujat esiintyv{t </tt/ii<data>> tai
</tt/ii<save>>-lauseessa.  Ne varataan staattisesti ja s{ilyv{t
koko ohjelman suorituksen ajan.  Mikrotasolla
rinnakkaistettavan silmukan alussa voidaan my|s ilmoittaa,
ett{ jotkin paikallisista muuttujista ovat jaettuja.  Jos
aliohjelmasta (tai silmukasta) on samanaikaisesti k{ynniss{
useita kopioita, ne voivat kaikki viitata n{ihin muuttujiin.
Ohjelmoijan on huolehdittava, ettei useampi prosessi p{{se
samanaikaisesti muuttamaan n{it{ muuttujia.

</bf Jaetut globaalit> muuttujat sijaitsevat tavallisella
</tt/ii<common>>-alueella. My|sk{{n niit{ ei useampi prosessi
saa p{{st{ muuttamaan samanaikaisesti.

Moniprosessoinnin ehk{ hankalin teht{v{ on p{{tt{{, mitk{
muuttujat on m{{ritelt{v{ jaetuiksi, ja miten ne on
suojattava.

Koska rinnakkaistetussa ohjelmassa samasta aliohjelmasta
voi olla samanaikaisesti k{ynniss{ useita kopioita, 
aliohjelman on oltava ns. /ii<vapaak{yntinen> (/ii<reentrant>).
T{m{ edellytt{{, ett{ aliohjelman paikalliset muuttujat
varataan uudestaan joka kerta aliohjelmaa kutsuttaessa.
Siksi </tt/ii<static>>-optiolla k{{nnetty{ ohjelmaa ei voi
rinnakkaistaa. /ii<Dynaamisen tilanvarauksen> vuoksi 
paikallisten muuttujien arvot eiv{t s{ily aliohjelmakutsujen
v{lill{ ja kaikki muuttujat on ennen k{ytt|{ ehdottomasti
muistettava alustaa.
 

/section</ii<Kriittiset alueet>>
 
Oletetaanpa, ett{ rinnakkaisessa ohjelman osassa esiintyy
lause:
 
</copymode
      sum = sum + x(i)
>
 
Jos ajoitus sattuu huonosti, voi k{yd{ n{in:
Prosessi 1 lukee muuttujan </tt sum> ja rupeaa lis{{m{{n
siihen omaa </tt x(i)>:n arvoaan. Heti per{{n prosessi
2 lukee my|s muuttujan </tt sum> ja lis{{ siihen oman </tt x(i)>:ns{.
Sitten prosessi 1 tallettaa p{ivitetyn muuttujan, ja hetken
kuluttua prosessi 2 tekee samoin onnellisen tiet{m{tt|m{n{,
ett{ t{ll{ v{lill{ </tt sum> on jo muuttunut, ja ett{ sen
tulisi lis{t{ t{t{ uutta arvoa.
 
T{llainen ohjelman osa on nimelt{{n </sl kriittinen alue>
(/ii<critical region>), ja se on suojattava niin, ett{
vain yksi prosessi kerrallaan voi suorittaa sit{.
Joissakin rinnakkaisprosessointiin tarkoitetuissa kieliss{
on omia kontrollirakenteita t{t{ varten.

Kriittisten alueiden suojaus voidaan toteuttaa esimerkiksi
</sl semaforeilla>.  /ii<Semafori> on periaatteessa laskuri,
johon liittyy jono.  Semaforeja k{sitell{{n Dijkstran
esitt{mill{ </sl P-> ja </sl V-operaatioilla>:
/iii<P-operaatio> /iii<V-operaatio> 

</copymode
      P(s): </bf if> (s /gt 0) 
               </bf then> s := s-1
               </bf else> 'j{{ jonoon odottamaan'
 
      V(s): </bf if> 'jonossa on s:{{ odottavia prosesseja'
               </bf then> 'p{{st{ ensimm{inen niist{ jatkamaan'
               </bf else> s := s+1
>
 
N{m{ operaatiot toteutetaan niin, ettei niit{ voi keskeytt{{, joten
toinen prosessi ei voi sotkea niiden suoritusta.  Semaforeja
(tai vastaavanlaisia olioita) k{ytet{{n riippumattomien
prosessien synkronointiin my|s yhden prosessorin
moniajoymp{rist|iss{.  Laskurin arvo kertoo itse asiassa,
kuinka monta kappaletta suojattavia resursseja on vapaana. 
Jos se on nolla, t{ytyy prosessin j{{d{ odottamaan. 
K{ytett{ess{ semaforia kriittisen alueen suojaamiseen, voi
laskuri saada vain arvot 0 (joku on suorittamassa kriittist{
aluetta, joten j{{d{{n odottamaan) tai 1 (kukaan ei juuri
nyt suorita kriittist{ aluetta, joten voidaan jatkaa). 


/section</ii<Prosessien synkronointi>>

Alkeellisemmissa moniprosessorij{rjestelmiss{ (esim. 
Alliant) varataan tarvittava m{{r{ prosessoreja koko ty|n
ajaksi, jolloin niiden hallinta yksinkertaistuu, mutta
samalla menetet{{n huomattava osa moniprosessorikoneen
suorituskyvyst{.  Crayn prosessorien hallinta sensijaan on
dynaamista.  Jos vapaista prosessoreista on puutetta,
voidaan rinnakkaiset osat suorittaa per{kk{in eri aikoina;
jos taas kone on muuten tyhj{, voivat kaikki prosessorit
suorittaa saman ohjelman palasia.  

Rinnakkaiset prosessit voivat edet{ varsin
sattumanvaraisesti koneen kuormituksesta riippuen.  T{m{
tekee mahdollisten virheiden j{ljitt{misen vaikeaksi, koska
virheellisesti rinnakkaistettu ohjelma voi eri kerroilla
toimia eri tavoin. 

Prosessien identifiointia varten on kutakin erillisen{
k{ynnistett{v{{ prosessia varten m{{ritelt{v{ olio, jota
voisimme kutsua </sl/ii<task-muuttuja>ksi>.  Kun aliohjelma
k{ynnistet{{n erillisen{ prosessina, siihen liitet{{n
task-muuttuja, jonka avulla kutsuva ohjelma voi seurata
aliohjelman tilaa. 

Rinnakkaiset aliohjelmat voivat my|s olla vuorovaikutuksessa
kesken{{n.  Niiss{ voi olla esimerkiksi osia, joita ei saa
suorittaa ennen kuin jokin toinen prosessi on ehtinyt
riitt{v{n pitk{lle.  T{llaiseen synkronointiin voidaan
k{ytt{{ esimerkiksi </sl/ii<tapahtumamuuttujia>>
(/ii<event>).  Prosessi voi kysy{, onko jokin tapahtuma
tapahtunut, ja ellei ole, se nukuttaa itsens{ odottamaan
tapahtumaa.  Kun sitten jokin toinen prosessi aiheuttaa
tapahtuman, her{{ sit{ odottava prosessi jatkamaan
toimintaansa. 


/section</ii<Macrotasking>>

Makrotason rinnakkaistaminen muistuttaa moniajoymp{rist|n
systeemiohjelmointia.  Ohjelma jaetaan k{sin
toisistaan riippumattomasti suoritettaviin palasiin.
Vaikeutena on n{iden ep{deterministisesti
etenevien prosessien synkronointi.

Crayn </sc fortran>issa makrotason rinnakkaistaminen
tapahtuu kutsumalla kirjastoaliohjelmia.  Lis{ksi on
m{{ritelt{v{ muutamia kokonaislukumuuttujia.  Prosessien
tunnistamiseen tarvittavat /ii<task-muuttuja>t ovat kahden tai
kolmen sanan mittaisia kokonaislukutaulukoita. 
Lis{ksi voidaan m{{ritell{ tapahtumamuuttujia ja
kriittisi{ alueita suojaavia lukkoja, jotka molemmat
ovat </sc fortran>-ohjelman kannalta yksinkertaisia
kokonaislukumuuttujia.

Rinnakkaistettuun ohjelmaan on linkitett{v{ mukaan
/ii<multitasking-kirjasto>t. Yksinkertaisimmassa tapauksessa
rinnakkaistetun ohjelman k{{nt{minen ja linkitys tapahtuu
komennoilla

</copymode
      cft77 prog.f
      segldr -L //lib//multi prog.o
>

T{ss{ </tt //lib//multi> on hakemisto, joka sis{lt{{
kirjastojen moniprosessoriversiot. Vaikka t{m{ kirjastom{{rittely
unohtuisikin, ei linkitt{j{ valitettavasti valita yht{{n mit{{n,
mutta ajo kaatuu suoritusaikana h{m{r{per{isell{ tavalla.

T{rkeimpi{ rinnakkaislaskentaan liittyvist{
aliohjelmista ovat seuraavat:

/page
</tt/ii<tskstart>(</sl<tsk>>,</sl<name>>/opt<,</sl<list>>>)>

K{ynnistet{{n aliohjelma </sl name> erillisen{ prosessina.
Prosessia kuvaava task-muuttuja on </sl tsk>. Muuttujaluettelo
</sl list> sis{lt{{ aliohjelman mahdolliset parametrit.
Task-muuttuja </sl tsk> on kahden tai kolmen sanan
kokonaislukutaulukko, jonka ensimm{iseen sanaan on ennen
</tt tskstart>in kutsua sijoitettava taulukon pituus
(siis 2 tai 3). Jos taulukossa on kolme alkiota, kolmanteen
sanaan voi sijoittaa jonkin tunnuksen eli korkeintaan
kahdeksan merkin merkkijonon.


</tt/ii<tskwait>(</sl<tsk>>)>

J{{d{{n odottamaan, kunnes task-muuttujaan </sl tsk> liittyv{
prosessi on p{{ttynyt.

</tt/ii<evasgn>(</sl<name>>/opt<,</sl<value>>>)>

M{{rittelee kokonaislukumuuttujan </sl name> event-muuttujaksi.

</tt/ii<evpost>(</sl<name>>)>

Aiheuttaa tapahtuman </sl name>. T{t{ tapahtumaa mahdollisesti
odottaville prosesseille ilmoitetaan, ett{ ne saavat jatkaa
suoritustaan.

</tt/ii<evwait>(</sl<name>>)>

Tutkitaan, onko tapahtuma </sl name> tapahtunut. Jos on,
jatketaan suoritusta. Mik{li </sl name> ei ole tapahtunut,
prosessi nukahtaa odottamaan sit{.

</tt/ii<evrel>(</sl<name>>)>

Poistaa tapahtuman </sl name> m{{rittelyn. Jos jokin
prosessi on odottamassa tapahtumaa tai yritt{{ k{ytt{{
sit{ joskus my|hemmin, ohjelman suoritus p{{ttyy virheeseen.
Vaikka t{m{ kutsu ei olekaan v{ltt{m{t|n, se on hyv{
apuv{line varmistamaan, ettei ohjelman synkronointi ole
aivan p{in seini{.

</tt/ii<lockasgn>(</sl<name>>/opt<,</sl<value>>>)>

M{{rittelee kokonaislukumuuttujat </sl name> kriittiseen
alueeseen liittyv{ksi lukoksi.

</tt/ii<lockon>(</sl<name>>)>

Testataan lukkoa </sl name>. Jos se on auki, se asetataan
p{{lle ja jatketaan suoritusta.
Prosessi aloittaa kriittisen alueen suorituksen, eik{
muita prosesseja saa p{{st{{ sinne samanaikaisesti.
Mik{li lukko on jo p{{ll{, prosessi j{{ jonoon odottamaan.
Kysymyksess{ on siis itse asiassa P-operaation toteutus.

</tt/ii<lockoff>(</sl<name>>)>

Avaa lukon </sl name>. Prosessi on t{ll|in p{{tt{nyt
kriittisen alueen suorituksen. Jos lukkoa odottaa
muita prosesseja (jotka siis ovat kutsuneet aliohjelmaa
</tt lockon>), niist{ ensimm{inen p{{stet{{n jatkamaan.
T{m{ vastaa V-operaatiota.

</tt/ii<lockrel>(</sl<name>>)>

Poistaa lukon </sl name>.  T{m{ kutsu on hyv{ sijoittaa
sellaiseen ohjelman kohtaan, jossa kaikkien prosessien
on t{ytynyt lopettaa kyseiseen lukkoon liittyv{n 
kriittisen alueen k{sittely. Mik{li jokin prosessi
viel{ odottaa lukkoa, ohjelman suoritus p{{ttyy
virheeseen.


Seuraavassa esimerkiss{ k{sitell{{n taulukot </tt a> ja
</tt b> samalla aliohjelmalla rinnakkaisesti. 
Aliohjelmat p{ivitt{v{t globaalia muuttujaa </tt x>,
joka siis on jaettu ja t{ytyy suojata.

</font/tt = cmtt8
/beginverbatim

      program parallel
      integer tsk1(2),tsk2(2),  ! task-muuttujat
     .        abloy             ! lukko kriittisen alueen suojaksi
      real a(10000),b(10000),x
      external sub
      common /c/x,abloy
      ...
      tsk1(1)=2                 ! taulukon pituus task-muuttujien
      tsk2(1)=2                 ! ensimm{iseen sanaan
      call lockasgn(abloy)      ! m{{ritell{{n abloy lukoksi
      call tskstart(tsk1,sub,a) ! k{ynnistet{{n sub(a)
      call tskstart(tsk2,sub,b) ! k{ynnistet{{n sub(b)
      ...                       ! tehd{{n jotain muuta v{lill{
      call tskwait(tsk1)        ! odotetaan, ett{ rinnakkaiset
      call tskwait(tsk2)        ! prosessit p{{ttyv{t
      call lockrel(abloy)       ! poistetaan lukon m{{rittely
      ...
      end

      subroutine sub(array)
      real array(10000)
      real x,z
      integer abloy
      common /c/x,abloy
      ...
      call lockon(abloy)        ! kriittinen alue alkaa
      x=x+z
      call lockoff(abloy)       ! kriittinen alue p{{ttyy
      ...
      end
/endverbatim>

P{{ohjelma k{ynnist{{ kaksi kopiota aliohjelmasta </tt sub>. 
Niiden suorituksen aikana p{{ohjelma voi jatkaa
toimintaansa.  P{{ohjelman tarvitessa aliohjelman tuloksia
sen on varmistuttava aliohjelman p{{ttymisest{ kutsumalla
</tt tskwait>i{.  

Koska kumpikin kopio aliohjelmasta haluaa p{ivitt{{ samaa
globaalia muuttujaa </tt x>, on kyseinen p{ivitys (</tt
x=x+z>) m{{ritelt{v{ kriittiseksi alueeksi ja suojattava
lukolla.  Aliohjelman kopiot voivat edet{ muuten toisistaan
t{ysin riippumatta, mutta t{t{ kriittist{ aluetta ne saavat
suorittaa vain yksi kerrallaan. 

Seuraava esimerkki esitt{{ er{st{ k{ytt|tapaa tapahtumamuuttujille:

/vbox<</font/tt = cmtt8
/beginverbatim
      program events
      integer tsk(2),ready
      real a(10000),b(10000),c(10000)
      external sub
      common /events/ready
      tsk(1)=2
      call evasgn(ready)
      call tskstart(tsk,sub,a,b)
      ...
      call evwait(ready)
      do 1 i=1,10000
         c(i)=a(i)...
  1   continue
      call tskwait(tsk)
      do 2 i=1,10000
         c(i)=c(i)+b(i)
  2   continue
      ...
      end

      subroutine sub(a,b)
      real a(10000),b(10000)
      integer ready
      common /events/ready
      do 1 i=1,10000
         a(i)= ...
 1    continue
      call evpost(ready)
      do 2 i=1,10000
         b(i)=...
 2    continue
      end
/endverbatim>>

T{ss{ k{ynnistet{{n rinnakkaisena prosessina aliohjelma 
</tt sub>, joka rupeaa laskemaan taulukoita </tt a> ja </tt b>.
P{{ohjelma voi samalla tehd{ jotain muuta. Kun aliohjelma
on saanut taulukon </tt a> valmiiksi, se aiheuttaa tapahtuman
</tt ready>. P{{ohjelma puolestaan odottaa tapahtumaa </tt ready>
ennen kuin se ryhtyy k{ytt{m{{n taulukkoa </tt a>. Taulukon
</tt b> valmistuminen voitaisiin ilmoittaa samalla tavoin
toisella tapahtumamuuttujalla. T{ss{ aliohjelma ei kuitenkaan
tee en{{ muuta </tt b>:n laskemisen j{lkeen, joten p{{ohjelma
voi yht{ hyvin j{{d{ v{ijym{{n koko aliohjelman p{{ttymist{.

Sek{ kriittisiin alueisiin ett{ tapahtumiin liittyy
prosessien lukkiutumisvaara (deadlock, deathly embrace). 
Ohjelmasta riippuen ongelma saattaa esiinty{ aina tai vain
prosessien edetess{ juuri sopivasti.  Seuraavassa (aika
keinotekoisessa) esimerkiss{ on kaksi kriittist{ aluetta,
jotka halutaan molemmat varata samanaikaisesti:

</font/tt = cmtt8
/beginverbatim

      ...
      call tskstart(tsk1,sub1)
      call tskstart(tsk2,sub2)
      ...
      subroutine sub1
      ...
      call lockon(abloy)
      call lockon(yale)
      ...
      call lockoff(yale)
      call lockoff(abloy)
      end

      subroutine sub2
      ...
      call lockon(yale)
      call lockon(abloy)
      ...
      call lockoff(abloy)
      call lockoff(yale)
      end
/endverbatim>

Jos toinen aliohjelma ehtii suorittaa kriittiset alueet
ennen kuin toinen pyyt{{ niit{, ei tapahdu mit{{n
h{iritsev{{.  Mutta jos </tt sub1> sattuu varaamaan 
</tt abloy>n samanaikaisesti kuin </tt sub2> varaa </tt yalen>,
ei </tt sub1> voi en{{ edet{ ennen kuin </tt sub2> vapauttaa
</tt yale>n, mit{ se ei tietenk{{n voi tehd{ ennen kuin se
p{{see suorittamaan kriittisen alueensa, mit{ se taas ei voi
tehd{ ennen kuin </tt sub1> vapauttaa </tt abloy>n ... 

T{ss{ esimerkiss{ ohjelma lukkiutuu vain prosessien osuessa
samanaikaisesti kriittiseen alueeseen.  Prosessi voi juuttua
my|s odottamaan tapahtumaa, jota ei koskaan tule esimerkiksi
siksi, ett{ ohjelmasta on unohtunut vastaava </tt evpost>,
tai kaksi prosessia odottaa toistensa aiheuttamia tapahtumia,
joita ne eiv{t voi aiheuttaa, koska kumpikaan ei p{{se
jatkamaan.


/section</ii<Microtasking>>

Makrotason rinnakkaistamisen ongelmien (k{ytt|j{rjestelm{n
yleisrasitteen kasvu ja prosessorien ep{tasainen kuormitus)
v{ltt{miseksi on moniprosessorikoneille kehitetty menetelmi{
mikrotason rinnakkaistamiseen. T{m{ tarkoittaa l{hinn{
yksitt{isten silmukoiden (yleens{ uloimpien) jakamista
usealle prosessorille.

Silmukan rinnakkaistamisesta aiheutuu samantapaisia
riippuvuusongelmia kuin vektoroinnistakin. Nyt vaatimus
iteraatiokierrosten riippumattomuudesta on kuitenkin
viel{ voimakkaampi. Koska eri prosessorit saattavat
suorittaa silmukan eri kierroksia mielivaltaisessa
j{rjestyksess{, on kaikkien iteraatiokierrosten oltava
toisistaan riippumattomia. Seuraava silmukka on esimerkki
vektoroituvasta silmukasta, jota ei voi rinnakkaistaa:
/iii<riippuvuus>

</copymode
      do 1 i=1,n
         x(i)=x(i+1)+1
  1   continue
>

Rinnakkaistettu versio t{st{ saattaisi edet{ seuraavassa
j{rjestyksess{

</copymode
      x(2)=x(3)+1
      x(1)=x(2)+1
>

mik{ selv{stikin tuottaisi v{{r{n tuloksen.

Silmukan rinnakkaistamisen edellytyksen{ siis on, ett{ se
on voitava tulosten muuttumatta suorittaa miss{ tahansa
j{rjestyksess{.

Mikrotason rinnakkaistamisessa synkronointi on suhteellisen
yksinkertaista.  Suurimpana vaikeutena on nyt muuttujien
v{listen riippuvuuksien ja niist{ aiheutuvien rajoitusten
ymm{rt{minen. 

Mikrotasolla voidaan rinnakkaistaa vain aliohjelmia, ei siis
p{{ohjelmaa eik{ funktioita. Kun rinnakkainen aliohjelma
k{ynnistyy, siit{ valitaan suoritettavaksi
yksiprosessoriversio tai useampia kopioita moniprosessoriversiosta.

Crayn </sc fortran>issa mikrotason rinnakkaistaminen
tapahtuu k{{nt{j{n /ii<direktiivi>en avulla. N{m{ direktiivit
ovat ohjelmarivej{, joiden alussa lukee </tt/ii<cmic/$>>.
Ne ovat siten periaatteessa kommentteja, eiv{tk{ haittaa
ohjelman k{{nt{mist{ muilla laitteistoilla.

Direktiivien mukaisesti aliohjelma jakaantuu ns.
/ii<kontrollirakenteisiin> (/ii<microtasking control structure>, /ii<MCS>),
jotka voidaan suorittaa rinnakkaisesti useammalla prosessorilla.
(T{t{ ei pid{ sekoittaa ohjelmointikielten rakenteita
tarkoittaviin kontrollirakenteisiin. Selvyyden vuoksi
k{yt{mmekin seuraavassa vain lyhennett{ MCS n{ist{ 
rinnakkaisuuteen liittyvist{ kontrollirakenteista.)

Jos ty| jaetaan useammalle prosessorille, jokainen niist{
joutuu suorittamaan oman kopionsa yhteisest{ koodista eli
MCS:ien ulkopuolisen osan.  MCS:n alkaessa eri prosessorit
k{sittelev{t vain oman osuutensa.  Kaikkien prosessorien on
saatava osansa MCS:st{ p{{t|kseen ennen kuin MCS:{{ seuraavan
yhteisen osan suoritus voi alkaa. /ii<Synkronointi> tapahtuu siis
automaattisesti kunkin MCS:n j{lkeen. MCS:n sis{ll{ 
suoritusj{rjestys sen sijaan on satunnainen.

Mikrotasolla rinnakkaistettu ohjelma on ennen varsinaista
k{{nn|st{ k{sitelt{v{ esiprosessorilla </tt/ii<premult>>,
joka tuottaa jokaisesta direktiivej{ sis{lt{v{st{
ohjelmamodulista kolme eri versiota:

1) Assembler-kielien master-kopio, joka on saman niminen
kuin alkuper{inen aliohjelma.  T{m{ p{{tt{{, k{ynnistet{{nk{
aliohjelmasta yksi- vai moniprosessoriversio. 
N{m{ assembler-modulit talletetaan tiedostoon </tt multc.s>,
joka on sitten k{{nnett{v{ </sc cal>-assemblerilla.

2) </sc Fortran>-kielinen yksiprosessoriversio.  T{m{n nimi
on alkuper{isen aliohjelman nimi jatkettuna kirjaimilla 
</tt sngl>.  Tarvittaessa nimi ktkaistaan kahdeksan merkin
mittaiseksi. 

3) Moniprosessoriversio. Sen nimi on puolestaan alkuper{inen
nimi + </tt mult>.

P{{ohjelma ja </sc fortran>-modulit talletetaan tiedostoon
</tt multf.f>, joka voidaan k{{nt{{ tavallisella 
</sc fortran>-k{{nt{j{ll{. 

Kaiken kaikkiaan k{{nn|s ja linkitys voisivat n{ytt{{
seuraavilta:

</copymode
      premult prog.f
      cft77 multf.f
      as multc.s
      segldr -L //lib//multi multf.o multc.o
>


T{rkeimpi{ mikrotason rinnakkaistamista ohjaavia
direktiivej{ ovat:

</tt cmic/$ /ii<getcpus> /opt</sl n>>

Ilmoitetaan, ett{ ohjelma voi k{ytt{{ korkeintaan
</sl n> prosessoria. Ellei </sl n>:{{ anneta, k{ytet{{n
ymp{rist|muuttujan </tt/ii<NCPUS>> ilmoittamaa m{{r{{.
T{m{ voidaan Bourne-shelliss{ asettaa sanomalla esimerkiksi

</copymode
      NCPUS=2
      export NCPUS
>

C-shelliss{ vastaava komento on

</copymode
      setenv NCPUS 2
>

</sc Unicos>-ymp{rist|ss{ </tt getcpus>-direktiivi ei ole
v{ltt{m{t|n, sill{ k{ytt|j{rjestelm{ osaa automaattisesti
varata sopivan m{{r{n prosessoreita.  Sit{ voidaan kuitenkin
k{ytt{{ rajoittamaan prosessorien m{{r{{.

</tt cmic/$ /ii<relcpus>>

Vapautetaan ylim{{r{iset prosessorit. 
T{t{ tarvitaan l{hinn{, jos ohjelma jatkuu viel{ pitk{{n,
mutta loppuosassa ei ole en{{ rinnakkaista koodia.

</tt cmic/$ /ii<micro>>

T{m{ ennen aliohjelmaa sijoitettava direktiivi ilmoittaa,
ett{ aliohjelma rinnakkaistetaan. Direktiivi merkitsee
siis h{ss{k{n (fray) alkua. 


</tt cmic/$ /ii<do global>>

Yleisin mikrotason rinnakkaistamisessa tarvittava direktiivi.
Ilmoittaa, ett{ seuraava </tt do>-silmukka voidaan suorittaa
rinnakkaisena.

</tt cmic/$ /ii<process>>

Aloittaa yhdell{ prosessorilla suoritettavan ohjelman osan.

</tt cmic/$ /ii<also process>>

Ilmoittaa, ett{ seuraava ohjelman osa voidaan suorittaa
rinnakkain toisen </tt process>-direktiivill{ m{{ritellyn
ohjelman osan kanssa. N{it{ direktiivej{ voi olla
useampia, yksi kutakin rinnakkaista ohjelman osaa kohti.

</tt cmic/$ /ii<end process>>

Ilmoittaa kohdan, jossa edellisill{ </tt process> ja
</tt also process> -direktiiveill{ m{{riteltyjen
rinnakkaisten ohjelman osien muodostama MCS p{{ttyy.

</tt cmic/$ /ii<guard> </sl n>>

Aloittaa kriittisen alueen </sl n>.

</tt cmic/$ /ii<end guard> </sl n>>

P{{tt{{ kriittisen alueen </sl n>.


Seuraavassa pieni esimerkki rinnakkaistettavasta 
aliohjelmasta:

/page
</font/tt = cmtt8
/beginverbatim

      cmic$ micro           ! rinnakkainen osa alkaa
             subroutine s(z)
             real x(10000)
             common /c/ x
             ...            ! kaikki prosessorit
             ...            ! suorittavat t{m{n
      cmic$ do global       ! MCS alkaa
             do 1 i=1,1000  ! kukin prosessori
                ...         ! suorittaa osansa
                x(i)=...    ! silmukasta
        1    continue       ! MCS p{{ttyy
             ...            ! kaikki suorittavat t{m{n
      cmic$ process         ! toinen MCS alkaa
             z=x(1)         ! vain yksi CPU suorittaa t{m{n
      cmic$ endprocess      ! toinen MCS p{{ttyy
             ...            ! ja taas kaikki tekev{t t{m{n
             return
             end            ! rinnakkainen osa p{{ttyy

/endverbatim>

Mikrotason rinnakkaistamisessa oleellisinta on huomata,
mitk{ muuttujat ovat jaettuja. Sellaisia ovat:

/beginitems

/item<-> </tt common>-alueella olevat muuttujat,
/item<-> </tt save>- ja </tt data>-lauseissa esiintyv{t muuttujat,
/item<-> aliohjelmalle parametrina v{litetyt muuttujat.

/enditems

Jaetut muuttujat ovat kaikkien prosessien tiedossa, joten
niit{ saa muuttaa vain MCS:n sis{ll{. T{m{ takaa sen, ett{
vain yksi prosessori p{{see muuttamaan jaettua muuttujaa.



/section</ii<Autotasking>>

Kuten edellisest{ lienee jo selvinnytkin, maailmassa on
monia yksinkertaisempia ja hauskempia asioita kuin
ohjelman rinnakkaistaminen.  Mutta ei tavallisenkaan 
ohjelmoijan silti tarvitse vaipua lineaarisen maailmankaikkeuden
yksitoikkoisuuteen. Apua tarjoaa ihan omia aikojaan 
rinnakkaistava k{{nt{j{ </tt cf77>.


Kaikessa yksinkertaisuudessaan ohjelman rinnakkaistaminen,
k{{nt{minen ja linkitys tapahtuu sanomalla

</copymode
      cf77 -Zp prog.f
>

T{m{n suoritus kest{{ jonkin aikaa ja tuloksena syntyy
suoritettava ohjelma </tt a.out>. 
Suorittamalla k{{nn|s komennolla

</copymode
      cf77 -ZP prog.f
>

j{{v{t syntyv{t v{litiedostot talteen, ja ohjelmoija voi
muuttaa niit{ tarvittaessa.

K{{nt{j{n parantunutta vektorointikyky{ kannattaa k{ytt{{
my|s yksiprosessorikoodille, jota ei ole aikomustakaan
rinnakkaistaa. Komento

</copymode
      cf77 -Zv prog.f
>

suorittaa ohjelmalle esiprosessoinnin, joka voi lis{t{
ohjelman vektoroituvuutta huomattavasti.

Automaattinen rinnakkaistaminen tapahtuu mikrotasolla.
Se poikkeaa kuitenkin jonkin verran tavallisesta
k{sin suoritettavasta mikrotason rinnakkaistamisesta.
 
Automaattisesti rinnakkaistava k{{nt{j{ k{ynnistyy
komennolla </tt/ii<cf77>>. T{m{ komento suorittaa nelj{
erillist{ vaihetta, (</tt fpp>, </tt fmp>, </tt cft77>,
</tt segldr>), joiden tuloksena syntyy suoritettava ohjelma.

Esiprosessori </tt/ii<fpp>> lis{{ ohjelmaan rinnakkaistamiseen
liittyv{t direktiivit.  Se huolehtii my|s vektorointiin
tarvittavasta riippuvuusanalyysist{, ja lis{{ ohjelmaan my|s
vektorointidirektiivej{.  T{m{ analyysi on monipuolisempi
kuin pelk{n </tt cft77>-k{{nt{j{n suorittama.  Esiprosessori
saattaa tehd{ koodiin huomattavia muutoksia, kuten vaihtaa
sisemm{n ja ulomman silmukan j{rjestyst{ (varsinainen
k{{nt{j{h{n vektoroi vain sisimpi{ silmukoita, toisin kuin
esimerkiksi IBM:n </sc vs-fortran>). 

Jos v{litiedostot s{ilytet{{n, esiprosessoinnin
tuloksena syntyy direktiiveill{ t{ydennetty
</sc fortran>-ohjelma (</tt prog.m>), jota ohjelmoija voi
edelleen muokata.

Toisessa vaiheessa, </tt/ii<fmp>>, muunnetaan rinnakkaistamiseen
liittyv{t direktiivit vastaavaksi </sc fortran>-koodiksi. 
Syntyv{ koodi saadaan haluttaessa tiedostoon </tt prog.j>,
ja sit{ voidaan muuttaa viel{ t{ss{kin vaiheessa.
Ohjelmakoodi ei kuitenkaan en{{ ole kovin luettavaa
lukuisten semaforiasetusten ja -testien vuoksi.

Seuraavaksi t{m{ tiedosto k{{nnet{{n objektikoodiksi
tavallisella </sc fortran>-k{{nt{j{ll{ </tt cft77>.
Koska raskain vaihe, riippuvuusanalyysi, on jo tehty,
k{{nn|s sujuu nyt suhteellisen nopeasti.
Tuloksena syntyy konekielinen ohjelma </tt fyle.o>.

Lopuksi kutsutaan linkitt{j{{, joka tekee
suoritettavaksi kelpaavan ohjelman. Sen nimeksi
tulee </sc unix>in standardin mukainen </tt a.out>.


/section<Esimerkki automaattisesta rinnakkaistamisesta>

Seuraava esimerkki on lyhennelm{ Crayn </sc fortran>-kurssin
er{{st{ harjoitusteht{v{st{. Katsomme, mit{
ohjelmalle tapahtuu, kun se k{{nnet{{n </tt cf77>:lla.

Alkuper{inen ohjelma on seuraavan n{k|inen:

</font/tt = cmtt8
/beginverbatim

      program exerc1
      parameter (n=100)
      common /tt/  b(n), c(n,n), d(n,n)
      b = ranf()
      c = ranf()
      d = ranf()
      call sub
      end

      subroutine sub
      parameter (n=100)
      common /tt/  b(n), c(n,n), d(n,n)
      do 999 kkk=1,1000
      do 100 j = 2, n
        a = b(j) - b(j-1)
        do 100 i = 2, n
          c(i,j) = a * c(i-1,j)+d(i,j)
100   continue
999   continue
      end
/endverbatim>

T{ss{ on ilmeinen rekursio </tt c>:n suhteen
</tt do 100> -silmukassa. Optimoitaessa koodia k{sin,
tulee ensimm{iseksi mieleen korvata </tt a> taulukolla,
laskea differenssit </tt b(j)-b(j-1)> valmiiksi ja
vaihtaa lopuksi </tt i>- ja </tt j>-silmukoiden j{rjestys,
jolloin rekursiota ei en{{ ole.

K{{nnet{{n ohjelma komennolla

</copymode
      cf77 -ZP koe.f
>

Optio </tt -ZP> tallettaa syntyv{t v{litiedostot 
</tt koe.m> ja </tt koe.j>, jotta voimme tutkia, kuinka
k{{nt{j{ on teht{v{st{{n selviytynyt.

Seuraavassa on tiedoston </tt koe.m> listaus:

</font/tt = cmtt8
/beginverbatim

      program exerc1
      parameter (n=100)
      common /tt/  b(n), c(n,n), d(n,n)
      b = ranf()
      c = ranf()
      d = ranf()
      call sub
      s = 0.0
      do 1 i=1,n
         s = s + c(i,i)
 1    continue
      write *,s
      end
 
      subroutine sub
      parameter (n=100)
C...TRANSLATED BY FPP 2.26B16 12/08/89  05:52:24    
      INTEGER J1S
      REAL A1U(100)
      REAL QQQ
      COMMON/Z1FPP0CM/ QQQ(65535)
      EQUIVALENCE (QQQ(1),A1U)
      common /tt/  b(n), c(n,n), d(n,n)
 
CMIC$ DO ALL SHARED(B, C, D) PRIVATE(KKK, J, A, I, A1U, J1S)
      DO 999 KKK = 1, 1000
CDIR$    IVDEP
         DO 77001 J = 1, 99
            A1U(J) = B(1+J) - B(J)
77001    CONTINUE
         DO 77002 I = 1, 99
CDIR$       IVDEP
            DO 77003 J = 1, 99
               C(1+I,1+J) = A1U(J)*C(I,1+J) + D(1+I,1+J)
77003       CONTINUE
77002    CONTINUE
  999 CONTINUE
 
      end

/endverbatim>

T{m{ muistuttaa viel{ aika paljon alkuper{ist{ ohjelmaa.
Havaitsemme siin{ tapahtuneen seuraavia muutoksia:

1) Sisemm{t silmukat on konvertoitu juuri siten, kuin
koodia k{sin viritett{ess{. Skalaari </tt a> on korvattu
taulukolla </tt A1U> ja silmukoiden j{rjestys on vaihdettu
rekursion poistamiseksi.

2) Vektoroituvien silmukoiden eteen on lis{tty direktiivit
</tt cdir/$ ivdep>, joten k{{nn|ksen my|hemmiss{ vaiheissa
niiss{ esiintyvi{ riippuvuuksia ei en{{ tarvitse tutkia.

3) Uloin silmukka on rinnakkaistettu, mik{ ilmenee sen
eteen ilmestyneest{ direktiivist{ </tt cmic/$ do all>.

Rinnakkaistettu versio ohjelmasta (</tt koe.j>) onkin
sitten jo huomattavasti pitempi ja monimutkaisempi:

</font/tt = cmtt8
/beginverbatim

      program exerc1
      parameter (n=100)
      common /tt/  b(n), c(n,n), d(n,n)
      b = ranf()
      c = ranf()
      d = ranf()
      call sub
      s = 0.0
      do 1 i=1,n
         s = s + c(i,i)
 1    continue
      write *,s
      end
c
c  sub on aliohjelman master-prosessorin versio, jota muut
c  prosessorit eiv{t suorita
c
      subroutine sub
C...Translated by fmp 3.1 (33)
      parameter (n=100)
C...TRANSLATED BY FPP 2.26B16 12/08/89  05:52:24    
      INTEGER J1S
      REAL A1U(100)
      REAL QQQ
      COMMON/Z1FPP0CM/ QQQ(65535)
      EQUIVALENCE (QQQ(1),A1U)
      common /tt/  b(n), c(n,n), d(n,n)
 
      integer utqarg1(0:3)
      integer utqrloc
      integer utqaloc
      integer utqoff
      pointer (utqcsnmp,utqcsnum)
      integer utqcsnum
      integer utqgcsnm
      integer utqtrips
      integer utqitr
      integer utqdummy
      integer g@offset
      integer g@micrtn
      integer g@micarg
      common /g@micrtn/g@micrtn
      common /g@micarg/g@micarg
      common /g@offset/g@offset
      external submm1              ! slave-prosessorien versio
      utqarg1(1) = loc@(b)         ! submm1:n argumenttien 
      utqarg1(2) = loc@(c)         ! osoitteet 
      utqarg1(3) = loc@(d)         ! (muuttujat b, c ja d)
      utqarg1(0) = 3               ! ja argumenttien lukum{{r{
CMICS DO ALL SHARED(B, C, D) PRIVATE(KKK, J, A, I, A1U, J1S)
      utqtrips = 1000 - 1
      if (utqtrips.gt.0) then
      utqoff = 1000 + 1
cdir$ suppress b,c,d               ! pakotetaan argumentit muistiin
      utqrloc = loc@(submm1)
      utqaloc = loc@(utqarg1)
      if (shiftl@(read@sm(),10).ge.0) then
      utqdummy = tset@sm(10)
      utqdummy = tset@sm(3)
      utqgcsnm = read@st(1)
      g@offset = utqoff
      g@micrtn = utqrloc
      g@micarg = utqaloc
cdir$ suppress  g@offset
cdir$ suppress  g@micrtn
cdir$ suppress  g@micarg
      utqdummy = write@st(2,-utqtrips)
      utqdummy = write@st(0,-1)
      utqdummy = write@st(1,compl@(utqgcsnm))
      utqdummy = clr@sm(12)
      utqdummy = wclr@sm(3)
      utqcsnmp = read@b(55) + 131
      utqcsnum = -1
      kkk = 1
    2 continue
c      DO 999 KKK = 1, 1000
CDIR$    IVDEP
         DO 77001 J = 1, 99
            A1U(J) = B(1+J) - B(J)
77001    CONTINUE
         DO 77002 I = 1, 99
CDIR$       IVDEP
            DO 77003 J = 1, 99
               C(1+I,1+J) = A1U(J)*C(I,1+J) + D(1+I,1+J)
77003       CONTINUE
77002    CONTINUE
  999 CONTINUE
 
      utqdummy = tset@sm(4)
      utqitr = read@st(2)
      utqdummy = write@st(2,utqitr+1)
      utqdummy = clr@sm(4)
      utqdummy = compl@(utqitr)
      kkk = utqoff + utqitr
      if (utqdummy.ge.0) goto 2
      utqdummy = tset@sm(3)
      utqdummy = set@sm(12)
      utqdummy = write@st(1,0-read@st(1))
      utqdummy = clr@sm(0)
      utqdummy = clr@sm(3)
    7 utqdummy = tset@sm(0)        ! odotetaan, ett{ kaikki
      if (read@sb(0).ne.0) goto 7  ! slave-prosessorit lopettaneet
      utqdummy = clr@sm(0)
      utqdummy = clr@sm(10)        ! autotasking p{{ttyy
      goto 3
      endif
      endif
CMICS DO ALL SHARED(B, C, D) PRIVATE(KKK, J, A, I, A1U, J1S)
c      DO 999 KKK = 1, 1000
      do 1 kkk = 1, 1000
cdir$ ivdep
      do4j=1,99
      a1u(j)=b(1+j)-b(j)
    4 continue
      do5i=1,99
cdir$ ivdep
      do6j=1,99
      c(1+i,1+j)=a1u(j)*c(i,1+j)+d(1+i,1+j)
    6 continue
    5 continue
    1 continue
    3 continue
      end
c
c  submm1 sis{lt{{ aliohjelman sub rinnakkaistetun silmukan
c  koodin, jonka slave-prosessorit suorittavat
c
      subroutine submm1(b,c,d)
cdir$ alloc=stack
      parameter(n=100)
      integer utqoff
      integer utqdummy
      integer g@offset
      integer utqitr
      real a1u(100)
      real b(*)
      real c(n,*)
      real d(n,*)
      common /g@offset/g@offset
      external park
      utqoff = g@offset
CMICS DO ALL SHARED(B, C, D) PRIVATE(KKK, J, A, I, A1U, J1S)
      utqdummy = tset@sm(4)
      utqitr = read@st(2)
      utqdummy = write@st(2,utqitr+1)
      utqdummy = clr@sm(4)
      utqdummy = compl@(utqitr)
      kkk = utqoff + utqitr
      if (utqdummy.ge.0) then
    2 continue
c      DO 999 KKK = 1, 1000
CDIR$    IVDEP
         DO 77001 J = 1, 99
            A1U(J) = B(1+J) - B(J)
77001    CONTINUE
         DO 77002 I = 1, 99
CDIR$       IVDEP
            DO 77003 J = 1, 99
               C(1+I,1+J) = A1U(J)*C(I,1+J) + D(1+I,1+J)
77003       CONTINUE
77002    CONTINUE
  999 CONTINUE
 
      utqdummy = tset@sm(4)
      utqitr = read@st(2)
      utqdummy = write@st(2,utqitr+1)
      utqdummy = clr@sm(4)
      utqdummy = compl@(utqitr)
      kkk = utqoff + utqitr
      if (utqdummy.ge.0) goto 2
      endif
      return
      end
/endverbatim>

Sitten vain suoritetaan ohjelma. Jotta saisimme selville
resurssien kulutuksen, k{ynnist{mme ensin raportointiohjelman </tt/ii<ja>>.
Jotta saisit nimenomaan yhden ajon tuloksen, on v{lill{ syyt{
nollata raportti komennolla </tt ja -t>.

</copymode
      ja -t
      ja
      a.out
>

T{m{n j{lkeen voimme komennolla 

</copymode
      ja -s
>

tulostaa k{ytt|raportin. T{ss{ tapauksessa se n{ytti
seuraavalta:

</font/tt = cmtt8
/beginverbatim

Job Accounting - Summary Report
===============================

Job Accounting File Name         : /tmp/.jacct244
Operating System                 : sn1109 sn1109 5.0 1109.1 CRAY Y-MP
User Name (ID)                   : hkarttun (107)
Group Name (ID)                  : csc (101)
Account Name (ID)                : a0011 (16)
Job ID                           : 244
Report Starts                    : 12/08/89 05:30:59
Report Ends                      : 12/08/89 05:37:29
Elapsed Time                     :          390      Seconds
User CPU Time                    :            6.8620 Seconds
Multitasking Breakdown

(Concurrent CPUs * Connect seconds = CPU seconds)
 ---------------   ---------------   -----------

               1 *          0.0009 =      0.0009
               2 *          0.0012 =      0.0024
               3 *          0.0047 =      0.0142
               4 *          0.1900 =      0.7599

(Concurrent CPUs * Connect seconds = CPU seconds)
      (Avg.)           (total)         (total)
 ---------------      --------------   -----------

            3.95 *          0.1968 =      0.7774

System CPU Time                  :            0.5912 Seconds
I/O Wait Time (Locked)           :            4.6982 Seconds
I/O Wait Time (Unlocked)         :            0.2388 Seconds
CPU Time Memory Integral         :            1.5228 Mword-seconds
SDS Time Memory Integral         :            0.0000 Mword-seconds
I/O Wait Time Memory Integral    :            1.5104 Mword-seconds
Data Transferred                 :            7.1876 Mbytes
Maximum memory used              :      1275904      Words
Logical I/O Requests             :          788
Physical I/O Requests            :          287
Number of Commands               :           29
Billing Units                    :            2.0703

/endverbatim>



/section<Mit{ ja miten pit{isi rinnakkaistaa>

Ennen mink{{nlaista rinnakkaistamista on ohjelman
toimittava kaikissa tilanteissa oikein my|s
k{{nnettyn{ oletusarvoisella </tt -a stack> -optiolla.
Yksiprosessoriversio on optimoitava ensin parantamalla
sen vektoroituvuutta, poistamalla muistikonfliktit jne.
Jos ohjelma edelleenkin vie paljon aikaa tai muistia,
sen rinnakkaistamista kannattaa harkita. Erityisesti
paljon muistia vievien ohjelmien rinnakkaistaminen
parantaa laitteiston suorituskyky{, vaikka yksitt{isten
ohjelmien CPU-ajat kasvavatkin.

Jotta /ii<makrotason rinnakkaistamisesta> olisi hy|ty{, on
ohjelman t{ytett{v{ seuraavat vaatimukset:

1) Rinnakkaisten ohjelman osien on oltava riitt{v{n isoja ja
toisistaan riippumattomia, ettei synkronoinnin aiheuttama
lis{rasite aiheutuisi kohtuuttoman suureksi. 

2) Rinnakkaisten osien on my|s viet{v{ suunnilleen saman
verran aikaa; muuten jotkin prosesseista viett{v{t suurimman
osan aikaa vain odottamalla toisten prosessien p{{ttymist{.

K{{nt{m{ll{ ohjelman flowtrace-optiolla (</tt<-ef>>) n{kee,
kuinka paljon aikaa ohjelma viett{{ eri aliohjelmissa.
Mik{li ohjelmasta l|ytyy palasia, jotka viev{t suunnilleen
yht{ paljon aikaa ja jotka voidaan suorittaa toisistaan
riippumatta, niiden rinnakkaistamista kannattaa ehk{ harkita.

Makrotason rinnakkaistamisen vaikeuksia ovat mm.

1) Ohjelmasta voi olla vaikeaa l|yt{{ riitt{v{n suuria
riippumattomia osia, jotta rinnakkaistaminen kannattaisi.

2) Prosessien synkronointi on usein ep{triviaali ongelma. 
Prosessit etenev{t ep{deterministisell{ tavalla, joten
kaikkien mahdollisuuksien testaaminen on mahdotonta. 
Samoin virhetilanteiden selvittely voi olla hyvin vaikeaa.

3) Rinnakkaistettu ohjelma sis{lt{{ standardiin kuulumattomia
aliohjelmakutsuja, joten ohjelma ei ole en{{ siirrett{viss{
ilman lis{ty|t{.

/ii<Mikrotason rinnakkaistamisen> etuja ovat:

1) Se on yleens{ helpompaa kuin makrotason rinnakkaistaminen,
koska synkronointi on yksinkertaista.

2) Lis{rasite on suhteellisen pieni.

3) Pientenkin osien rinnakkaistaminen kannattaa.

4) Pienten prosessien avulla laitteiston kokonaiskapasiteetti
pystyt{{n hy|dynt{m{{n paremmin.

5) Rinnakkaistaminen tapahtuu direktiiveill{, jotka eiv{t
haittaa ohjelman k{{nt{mist{ muilla laitteistoilla.

My|s mikrotason rinnakkaistaminen edellytt{{ yksityisten
ja jaettujen muuttujien huolellista erottamista.
Haittatekij{n{ on yhteisten ohjelman osien
suorittaminen kaikilla ty|h|n osallistuvilla prosessoreilla.
Mik{li rinnakkaiset osat eiv{t ole riitt{v{n suuria koko
aliohjelmaan n{hden, saattaa t{m{ redundanssi koitua
liian suureksi.

/ii<Automaattinen rinnakkaistaminen> on mikrotason rinnakkaistamista,
jonka etuja edell{ lueteltujen lis{ksi ovat:

1) Eritt{in helppoa, koska useimmissa tapauksissa k{ytt{j{n
ei tarvitse tehd{ ohjelmalleen mit{{n.

2) Parantunut vektoroituvuus.

3) Pienempi redundanssi kuin k{sin tehdyss{ rinnakkaistamisessa,
koska kaikki prosessorit eiv{t en{{ suorita yhteist{ koodia. 
