Numeriikan kirjastoja + Säästää aikaa, hikeä ja kyyneleitä + Aliohjelmat testattuja ja luotettavia + Tehokkuus optimoitu - Ei aina sovellu kovin hyvin omaan tehtävään - Kaupallisista kirjastoista ei saa lähdekoodia, ei voi muuttaa eikä siirtää erilaiseen ympäristöön BLAS, Lapack ilmaisia, lähdekoodi vapaasti saatavissa ja levitettävissä. Nag, IMSL kaupallisia, saatavana useisiin eri ympäristöihin. Melko kalliita, yleensä vuosittainen lisenssimaksu. Suurten tietokoneiden valmistajilla usein omille laitteille viritettyjä kirjastoja. Netlib (www.netlib.org) sisältää runsaasti eri alojen ilmaisia kirjastoja. CSC:n koneilla käytettävissä suuri määrä eri alojen kirjastoja ja ohjelmia. Verkkosivuilta (www.csc.fi) löytyy lisää linkkejä.
Ilmaisia kirjastoja BLAS (Basic lineara algebra subprograms) Yksinkertaisia vektori- ja matriisioperaatioita: - BLAS 1: skalaaritulo, vektorin normi, yleistetty yhteenlasku (SAXPY, s = ax + y). - BLAS 2: matriisin ja vektorin tulo, yhtälöryhmän ratkaisu, kun kerroinmatriisi on ylä- tai alakolmiomuotoa. - BLAS 3: matriisien tulot Lapack - lineaarisen yhtälöryhmän ratkaisu - matriisin ominaisarvojen laskeminen - lineaarinen pienimmän neliösumman ratkaisu - erilaisten hajotelmien laskeminen Rutiinit reaali- ja kompleksiarvoisille matriiseille.
Kaupallisia kirjastoja Numerical Recipes Ohjelmien lähdekoodi useilla ohjelmointikielillä. IMSL (www.vni.com) Ainakin tuhatkunta rutiinia. - BLAS - interpolointi - epälineaaristen yhtälöiden ratkaisu - integrointi - differentiaaliyhtälön ratkaisu - optimointi - erikoisfunktiot - tilastolliset testit - varianssianalyysi - aikasarja-analyysi - faktorianalyysi - todennäköisyysjakaumat Nag (www.nag.co.uk) Vielä laajempi kuin IMSL. Kirjaston mukana esimerkkiohjelma jokaisen aliohjelman käytöstä.
Rinnakkaislaskentaa Ohjelman toimintaa voidaan nopeuttaa suorittamalla useita asioita samanaikaisesti eri prosessoreilla. Tällaiseen laskentaan liittyy aivan omat ongelmansa, kuten eri prosessien synkronointi. Asian yksityiskohtainen käsittely ei kuulu tälle kurssille, sillä rinnakkaislaskennan tehokas käyttö edellyttää jo kohtalaista ohjelmointikokemusta. (Huonosti toteutettu rinnakkaistaminen voi jopa hidastaa ohjelman suoritusta!) Siksi tässä käsitelläänkin vain lyhyesti joitakin peruskäsitteitä. Monet peruskäsitteet liittyvät jo yhden prosessorin moniajoympäristöön, jossa eri prosessit saavat prosessorilta satunnaisia aikaviipaleita. Siksi seuraavassa ei erityisemmin erotella prosessia ja proasessoria. Algoritmisissa kielissa lauseiden suoritusjärjestys on tarkasti määrätty. Järjestyksellä ei kuitenkaan aina ole merkitystä. do i=1,100 a(i) = i end do Tässä silmukan kierrokset ovat toisistaan riippumattomia, joten ne voitaisiin suorittaa missä järjestyksessä tahansa. Toisistaan riippumattomat lauseet voidaan hajauttaa suoritettavaksi eri prosessoreilla. SISD = Single Instruction Single Data, perinteinen arkkitehtuuri SIMD = Single Instruction Multiple Data, suoritetaan sama ohjelman osa eri tiedoille (esimerkiksi taulukon alkioille, kuten vektoriprosessoreissa) MIMD = Multiple Instruction Multiple Data, Eri prosessorit voivat suorittaa eri ohjelmia eri datalle SMP = Symmetric MultiProcessor, Eri prosessoreilla on yhteinen keskusmuisti MMP = Massively Parallel Processor, Kullakin prosessorilla on oma muistinsa
Vaikeuksia Ohjelmointi on paljon mutkikkaampaa kuin yhden prosessorin tapauksessa. Rinnakkaisten osien suoritusjärjestys voi vaihdella satunnaisella tavalla. Suoritus synkronoitava tarvittaessa. Kaikkia eri järjestyksiä mahdotonta testata. Miten taata, ettei mikään suoritusjärjestys johda virhetilanteeseen? Varmistettava, ettei useampi prosessori yritä muuttaa samaa muuttujaa samaan aikaan. Eri prosessorit saattavat jäädä odottamaan toisiaan, jolloin ohjelma ei pääse jatkumaan (deadlock).
Amdahlin laki: suoritus nopeutuu kertoimella s = 1 1 v + v, k missä k on prosessorien määrä ja v rinnakkaistuvan koodin osuus. 10 s k = 10 5 k = 5 0.0 0.2 0.4 0.6 0.8 1.0 V/T Vektoroitumaton / rinnakkaistumaton osa koodia vaikuttaa merkittävästi suoritusaikaan.
Vektoriprosessori ei ole moniprosessorikone. Laskutoimitus voidaan jakaa vaiheisiin. Eri vaiheita voidaan suorittaa eri luvuille samaan aikaan. Ensimmäisen laskutoimituksen laskeminen vie normaalin ajan, mutta sen jälkeen uusi tulos valmistuu joka kellojaksolla (liukuhihnoitus, pipelining). Esimerkiksi taulukko-operaatio c = a+b: a(1) b(1) a(2) b(2) a(3) b(3) a(4) b(4) a(5) b(5) a(6) b(6) c(1) c(2) 0 1 2 3 4 5 aika
Monet peruskäsitteet liittyvät jo yhden prosessorin moniajoympäristöön, jossa eri prosessit saavat prosessorilta satunnaisia aikaviipaleita. Seuraavassa ei erityisemmin erotella prosessia ja proasessoria. ıkriittinen alue on ohjelman osa, jota vain yksi prosessi kerrallaan saa suorittaa. Esimerkiksi usea prosessi voi päivittää samaa tietorakennetta. Samanaikaiset päivitykset on estettävä. Esimerkiksi lukko, kokonaisluku (0=auki, 1=kiinni): LOCK(s) kriittinen alue UNLOCK(s) LOCK jää odottamaan, kunnes muuttuja on 0. Muuttujan arvon testaus ja asetus on toteutettava jakamattomana operaationa, jota ei voi keskeyttää. Konekielissä on yleensä tarkoitukseen sopiva käsky. Aktiivinen odotus: prosessi testaa lukkoa jatkuvasti, kunnes pääsee jatkamaan suoritusta.
Kriittinen alue voidaan suojata myös ısemaforilla. Semafori on tietorakenne, jossa on kokonaisluku ja jono suoritusta odottaville prosesseille. Semaforia voidaan käsitellä Dijkstran P- ja V-operaatioilla. P(sem): if (sem == 1) sem = 0 else jaa jonoon odottamaan V(sem): if (jonossa semaforia odottavia prosesseja) kaynnist jonosta seuraava prosessi else sem = 1 P ja V itse toteutettava jakamattomina operaatioina eli kriittisinä alueina. Odottamaan jäänyt prosessi ei kuluta prosessoriaikaa. Kun krittinen alue vapautuu, prosessi siirtyy ready-jonoon eli on valmiina jatkamaan suoritusta.
Synkronointi Moniprosesstoriarkkitehtuurissa ohjelman toisistaan riippumattomat osat voidaan suorittaa samaan aikaan eri prosessoreilla. a = sin(x) b = sqrt(x) c = a + b Kaksi ensimmäistä lausetta voidaan suorittaa samanaikaisesti. Kolmas lause voidaan suorittaa vasta, kun edellisten suoritus on päättynyt. Muuten tulos on satunnainen riippuen siitä, onko muuttujien a ja b arvot ja muutettu. Tarvitaan ısynkronointia, jolla varmistetaan, että hajautetun ohjelman haarat ovat päättyneet ennen niistä riippuvan ohjelman osan aloitusta.
Prosessit voivat kommunikoida keskenään lähettämällä viestejä. ohjelma 1: tuota dataa signal(valmista)... ohjelma 2:... wait(valmista) kuluta dataa... Ohjelma 2 jää odottamaan, kunnes se saa halutun viestin.
Riippuvuudet Ohjelman lauseiden väliset riippuvuudet rajoittavat rinnakkaistamista do i=2,100 a(i) = a(i-1)+1 end do Taulukon kukin alkio riippuu edellisellä kierroksella lasketusta. Kyseessä ırekursio, joka estää rinnakkaistamisen (ja vektoroinnin). Seuraava vektoroituu, koska silmukka ei riipu aikaisemmilla kierroksilla lasketuista tuloksista. do i=1,100 a(i) = a(i+1)+1 end do Rinnakkaistaminen ei onnistu ilman lisäehtoja. Seuraavassa ei ole mitään riippuvuutta: do i=1,100 a(i) = a(i+100)+1 end do
Kääntäjä pystyy havaitsemaan monet riippuvuudet automaattisesti. Mikäli riippuvuus on mahdollinen, silmukkaa ei vektoroida / rinnakkaisteta. Seuraavassa riippuvuutta ei voi päätellä, koska se riippuu muuttujan n arvosta: do i=1,100 a(i) = a(i+n)+1 end do Kääntäjälle voi kertoa direktiiveillä, että silmukan voi turvallisesti vektoroida / rinnakkaistaa. Direktiivit ovat tietyn muotoisia kommentteja, joten ne eivät vaikuta ohjelman kääntämiseen muissa ympäristöissä. Monilla laitevalmistajilla on omia kääntäjiä, joita ohjataan niiden omilla direktiiveillä. Tarkistettava erikseen kunkin kääntäjän manuaalista.
HPF High Performance Fortran on datarinnakkaiseen laskentaan (SIMD) tarkoitettu Fortranin laajennus. Itse kieleen liittyviä muutoksia (kuten forall-lause) on mukana jo F95:ssä. Mielivaltaisten ohjelman osien suorittaminen rinnakkain ei ole mahdollista. Toimintaa ohjataan direktiiveillä: real, dimension(1000):: a!hpf$ DISTRIBUTE a (BLOCK) do i=1,1000 a(i)=sqrt(i/1000.0) end do Tämä antaa ohjeen jakaa taulukko a samankokoisiin lohkoihin eri prosessoreille. Lohkon koko voidaan ilmoittaa:!hpf$ DISTRIBUTE (BLOCK(200)) :: a Taulukon alkiot voidaan jakaa myös syklisesti, jolloin präkkäiset alkiot joutuvat aina eri prosessoreille:!hpf$ DISTRIBUTE a (CYCLIC)
Prosessorien määrä voidaan määritellä direktiivillä PROCESSORS: real, dimension(1000) :: a!hpf$ PROCESSORS procs(10)!hpf$ DISTRIBUTE (BLOCK) ONTO procs :: a 10 prosessoria, joista procs(1):n muistiin talletetaan alkiot a(1:100) jne. Direktiivillä voidaan myös määritellä, miten prosessorit on kytketty toisiinsa (eli topologia).
Direktiivillä ALIGN voidaan varmistaa, että eri taulukot jaetaan samalla tavoin eri prosessorien kesken: real, dimension(1000) :: a, b, c, d!hpf$ DISTRIBUTE a(block)!hpf$ ALIGN WITH a :: b, c a = b+c!hpf$ ALIGN WITH a(1) :: d(10) d(10:1000) = a(1:991) Taulukot kannattaa kohdistaa niin, että laskutoimitukset operoivat samalla prosessorilla oleviin taulukoihin, jolloin ei tarvita tiedon siirtoa eri prosessorien muistien välillä.
Jos silmukassa on funktiokutsu, sillä saattaa olla sivuvaikutuksia, jolloin silmukkaa ei voi rinnakkaistaa: do i=1,100 a(i) = funk(b(i)) end do Funktiolle voidaan antaa määre pure, jolloin sillä ei saa olla sivuvaikutuksia, ja silmukan voi rinnakkaistaa. Mahdollinen F95:ssä. pure function funk(x) real, intent(in) :: x real :: funk funk = sqrt(sin(x)) end function Direktiivillä independent voidaan ilmoittaa, että silmukan kierrokset ovat toisistaan riippumattomia:!hpf$ INDEPENDENT do i=1,100 a(i) = a(i+n)+1 end do
MPI Message-Passing Interface on standardoitu viestinvälityskirjasto. Eri prosessoreilla toimivat ohjelmat välittävät toisilleen viestejä. Viesteillä voidaan siirtää dataa ja synkronoida ohjelmat. Seuraava esimerkki on CSC:n MPI-oppaasta. Samasta ohjelmasta käynnistetään kopiot useilla eri prosessoreilla. PROGRAM esimerkki IMPLICIT NONE INCLUDE mpif.h INTEGER, PARAMETER :: tag = 50 INTEGER :: id, ntasks, source_id, & dest_id, rc, i INTEGER, DIMENSION(MPI_STATUS_SIZE) :: status INTEGER, DIMENSION(2) :: msg CALL MPI_INIT(rc) IF (rc /= MPI_SUCCESS) THEN WRITE(*,*) MPI initialization failed STOP END IF CALL MPI_COMM_SIZE(MPI_COMM_WORLD, ntasks, rc) CALL MPI_COMM_RANK(MPI_COMM_WORLD, id, rc) IF (id /= 0) THEN msg(1) = id msg(2) = ntasks dest_id = 0 CALL MPI_SEND(msg, 2, MPI_INTEGER, dest_id, & tag, MPI_COMM_WORLD, rc) ELSE DO i = 1, ntasks-1 CALL MPI_RECV(msg, 2, MPI_INTEGER, & MPI_ANY_SOURCE, tag, & MPI_COMM_WORLD, status, rc) source_id = status(mpi_source) WRITE(*,*) message:, msg, sender:, source_id END DO END IF CALL MPI_FINALIZE(rc) END PROGRAM
Kaikissa kutsuissa viimeinen parametri (esimerkissä rc) on paluukoodi, joka ilmoittaa, onnistuiko toimenpide. Arvo on MPI SUCCESS, jos kaikki kunnossa. MPI INIT) alustaa rinnakkaistyön. MPI FINALIZE lopettaa rinnakkaisen osan. CALL MPI COMM SIZE ilmoittaa, kuinka monta prosessia työn käytössä on. CALL MPI COMM RANK ilmoittaa kutsuvan prosessin järjestysnumeron. MPI SEND lähettää viestin toiselle prosessille. MPI RECV odottaa, kunnes saa halutun viestin toiselta prosessilta. SEND ja RECV ovat pysähtyviä (blocking): aliohjelmakutsusta palataan jatkamaan suoritusta vasta, kun koko viesti on siirretty. On myös pysähtymättömät versiot, jolloin suoritus jatkuu välittömästi. Nopeampaa, mutta synkronoinnista on huolehdittava erikseen. Kahden prosessin välillä suoraan välitetyt viestit saapuvat aina samassa järjestyksessä kuin ne on lähetetty. Jos viesti kulkee useamman prosessin kautta, järjestys ei välttämättä säily.
MPI REDUCE kokoaa samanttyyppisen tiedon kaikilta prosesseilta ja suorittaa niille jonkin operaation. Esimerkiksi eri prosessir voivat laskea sarjakehitelmän palasia, jotka kootaan yhteen ja lasketaan niiden summa. MPI BCAST (broadcast) lähettää saman tiedon kaikille prosesseille. MPI SCATTER lähettää eri dataa eri prosesseille. Tyypillinen käyttö taulukon osien jakelu prosesseille. MPI GATHER edelliselle käänteinen operaatioa; esimerkiksi kerätään yhteen taulukon palaset eri prosesseilta. MPI BARRIER synkronointirutiini; prosessit jäävät odottamaan, kunnes kaikki niistä ovat päässeet tähän kohtaan.