Rinnakkaisuus. Tarkastelemme, miten algoritmien suoritusta voi nopeuttaa käyttämällä useaa laskentayksikköä samanaikaisesti.

Samankaltaiset tiedostot
Rinnakkaistietokoneet luento S

Algoritmit 1. Luento 2 Ke Timo Männikkö

Algoritmit 1. Luento 11 Ti Timo Männikkö

Algoritmit 1. Luento 3 Ti Timo Männikkö

4 Tehokkuus ja algoritmien suunnittelu

AVL-puut. eräs tapa tasapainottaa binäärihakupuu siten, että korkeus on O(log n) kun puussa on n avainta

Algoritmit 1. Demot Timo Männikkö

Luku 8. Aluekyselyt. 8.1 Summataulukko

58131 Tietorakenteet (kevät 2009) Harjoitus 9, ratkaisuja (Antti Laaksonen)

Algoritmit 2. Luento 8 To Timo Männikkö

Algoritmit 2. Luento 14 Ke Timo Männikkö

Nopea kertolasku, Karatsuban algoritmi

Tarkennamme geneeristä painamiskorotusalgoritmia

Algoritmit 1. Demot Timo Männikkö

Tietorakenteet, laskuharjoitus 10, ratkaisuja. 1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

7.4 Sormenjälkitekniikka

58131 Tietorakenteet ja algoritmit (syksy 2015)

Rinnakkaisuuden hyväksikäyttö peleissä. Paula Kemppi

1. (a) Seuraava algoritmi tutkii, onko jokin luku taulukossa monta kertaa:

ALGORITMIT 1 DEMOVASTAUKSET KEVÄT 2012

Esimerkkejä vaativuusluokista

Algoritmit 1. Luento 1 Ti Timo Männikkö

Algoritmit 1. Luento 10 Ke Timo Männikkö

2. Seuraavassa kuvassa on verkon solmujen topologinen järjestys: x t v q z u s y w r. Kuva 1: Tehtävän 2 solmut järjestettynä topologisesti.

Rinnakkaistietokoneet luento S

On annettu jono lukuja tai muita alkioita, joiden välille on määritelty suuruusjärjestys. Tehtävänä on saattaa alkiot suuruusjärjestykseen.

58131 Tietorakenteet ja algoritmit (kevät 2016) Ensimmäinen välikoe, malliratkaisut

Tietorakenteet, laskuharjoitus 2,

Algoritmit 1. Luento 5 Ti Timo Männikkö

Algoritmit 2. Luento 2 To Timo Männikkö

Algoritmit 2. Luento 7 Ti Timo Männikkö

Tietorakenteet ja algoritmit - syksy

1.4 Funktioiden kertaluokat

4. Joukkojen käsittely

811312A Tietorakenteet ja algoritmit, , Harjoitus 3, Ratkaisu

58131 Tietorakenteet ja algoritmit (kevät 2014) Uusinta- ja erilliskoe, , vastauksia

TIE Tietorakenteet ja algoritmit 25

Algoritmit 2. Luento 13 Ti Timo Männikkö

811312A Tietorakenteet ja algoritmit , Harjoitus 2 ratkaisu

58131 Tietorakenteet ja algoritmit (kevät 2013) Kurssikoe 2, , vastauksia

Pikalajittelu: valitaan ns. pivot-alkio esim. pivot = oikeanpuoleisin

Algoritmi on periaatteellisella tasolla seuraava:

Algoritmit 2. Luento 2 Ke Timo Männikkö

9 Erilaisia tapoja järjestää

Rinnakkaistietokoneet luento S

A ja B pelaavat sarjan pelejä. Sarjan voittaja on se, joka ensin voittaa n peliä.

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Algoritmit 2. Demot Timo Männikkö

Algoritmit 1. Luento 10 Ke Timo Männikkö

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Diskreetin matematiikan perusteet Laskuharjoitus 2 / vko 9

Verkon värittämistä hajautetuilla algoritmeilla

Matematiikan tukikurssi, kurssikerta 3

Luku 6. Dynaaminen ohjelmointi. 6.1 Funktion muisti

Algoritmit 1. Luento 13 Ti Timo Männikkö

58131 Tietorakenteet Erilliskoe , ratkaisuja (Jyrki Kivinen)

f(n) = Ω(g(n)) jos ja vain jos g(n) = O(f(n))

58131 Tietorakenteet ja algoritmit (syksy 2015) Toinen välikoe, malliratkaisut

Tietorakenteet, laskuharjoitus 1,

Algoritmit 2. Luento 13 Ti Timo Männikkö

Ohjelmoinnin peruskurssi Y1

Algoritmien suunnittelu ja analyysi (kevät 2004) 1. välikoe, ratkaisuja

lähtokohta: kahden O(h) korkuisen keon yhdistäminen uudella juurella vie O(h) operaatiota vrt. RemoveMinElem() keossa

Algoritmit 1. Luento 12 Ke Timo Männikkö

3. Laskennan vaativuusteoriaa

Tietorakenteet, laskuharjoitus 3, ratkaisuja

Olkoon S(n) kutsun merge-sort(a, p, q) tilavaativuus kun p q + 1 = n. Oletetaan merge toteutetuksi vakiotyötilassa (ei-triviaalia mutta mahdollista).

Algoritmit 1. Luento 12 Ti Timo Männikkö

Algoritmit 1. Luento 8 Ke Timo Männikkö

Algoritmit 1. Luento 14 Ke Timo Männikkö

2.1. Tehtävänä on osoittaa induktiolla, että kaikille n N pätee n = 1 n(n + 1). (1)

TKT20001 Tietorakenteet ja algoritmit Erilliskoe , malliratkaisut (Jyrki Kivinen)

Hakupuut. tässä luvussa tarkastelemme puita tiedon tallennusrakenteina

Valitaan alkio x 1 A B ja merkitään A 1 = A { x 1 }. Perinnöllisyyden nojalla A 1 I.

Diskreetin matematiikan perusteet Laskuharjoitus 1 / vko 8

Graafin 3-värittyvyyden tutkinta T Graafiteoria, projektityö (eksakti algoritmi), kevät 2005

Verkon virittävät puut

Johdatus lukuteoriaan Harjoitus 2 syksy 2008 Eemeli Blåsten. Ratkaisuehdotelma

CUDA. Moniydinohjelmointi Mikko Honkonen

Algoritmit 1. Luento 13 Ma Timo Männikkö

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

Oikeasta tosi-epätosi -väittämästä saa pisteen, ja hyvästä perustelusta toisen.

ja λ 2 = 2x 1r 0 x 2 + 2x 1r 0 x 2

11. Javan toistorakenteet 11.1

Johdatus diskreettiin matematiikkaan Harjoitus 5, Ratkaise rekursioyhtälö

(d) 29 4 (mod 7) (e) ( ) 49 (mod 10) (f) (mod 9)

1 Erilaisia tapoja järjestää

Matematiikan tukikurssi

Muistutus aikatauluista

4. Algoritmien tehokkuus

Algoritmit 2. Luento 5 Ti Timo Männikkö

Ohjelmoinnin peruskurssi Y1

58131 Tietorakenteet (kevät 2009) Harjoitus 11, ratkaisuja (Topi Musto)

1 + b t (i, j). Olkoon b t (i, j) todennäköisyys, että B t (i, j) = 1. Siis operaation access(j) odotusarvoinen kustannus ajanhetkellä t olisi.

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä

Tietotekniikan valintakoe

Fibonacci-kasoilla voidaan toteuttaa samat operaatiot kuin binomikasoilla.

Algoritmit 2. Luento 10 To Timo Männikkö

Algoritmit 2. Luento 3 Ti Timo Männikkö

811120P Diskreetit rakenteet

Transkriptio:

Rinnakkaisuus Tarkastelemme, miten algoritmien suoritusta voi nopeuttaa käyttämällä useaa laskentayksikköä samanaikaisesti. Miksi rinnakkaisuus on tärkeää? Millaisia nopeutuksia rinnakkaistamalla ylipäänsä voi saada aikaan? Mitä korkean tason työkaluja Java 8 tarjoaa rinnakkaisuuden saamiseksi käyttöön? Millaisia tekniikoita joissain tärkeissä erityisesti rinnakkain suoritettavaksi tarkoitetuissa algoritmeissa käytetään? Rinnakkaisuus ei kuulu koealueeseen, mutta siitä tulee yksi ylimääräinen laskuharjoitustehtävä. 1

Rinnakkaisuus ja samanaikaisuus Rinnakkaisuudella (parallelism) tarkoitetaan laskennan nopeuttamista jakamalla se useaan mahdollisimman riippumattomaan osaan ja suorittamalla kukin osa omalla laskentalaitteellaan (prosessorilla, ytimellä tms.). Samanaikaisuudella (concurrency) tarkoitetaan yleisemmin sitä, että useita asioita tapahtuu samaan aikaan. Tämä ei välttämättä edellytä, että käytössä on usea prosessori: tietokanta, jolla on useita käyttäjiä käyttäjällä voi olla samaan aikaan auki tekstinkäsittelyohjelma ja verkkoselain, ja tausta-ajona jotain tieteellistä laskentaa. Samanaikaisuuden hallinnassa keskeistä on yleensä varmistaa, että järjestelmän eri osien vuorovaikutukset toimivat tarkoitetulla tavalla: eri käyttäjien voivat yrittää lukea ja kirjoittaa samaa tietokannan tietoalkiota tekstinkäsittelyohjelmalla on suurempi prioriteetti kuin tausta-ajolla. 2

Tämän luennon tarkoituksena on antaa joitain ajatuksia siitä, miten rinnakkaisuus voi nopeuttaa laskentaa sen tyyppisissä tilanteissa, joita tällä kurssilla muutenkin on käsitelty. algoritmit korkean tason pseudokoodina esitellään vastaavia korkean abstraktiotason piirteitä Javassa emme tarkastele rinnakkaisuuden toteutusta (esim. prosessien välistä kommunikointia) emme tarkastele hajautetun laskennan erityispiirteitä. 3

Mistä peruskäyttäjä saa rinnakkaista laskentavoimaa? Tyypillisessä henkilökohtaisen tietokoneen keskussuorittimessa (CPU) voi nykyään olla esim. neljä ydintä (core). Tämän esityksen kannalta jokainen ydin on itsenäinen laskentalaite. Yksinkertaisuuden vuoksi käytämme jatkossa termiä suoritin (tai prosessori ) tällaisesta laskentalaitteesta, joka siis voi olla myös yksi ydin moniydinsuorittimessa. Grafiikkasuorittimessa (GPU) on hyvin suuri määrä rinnakkaisuutta (satoja ytimiä), mutta niiden toiminnot ovat melko erikoistuneita. Grafiikkasuoritinta voi kuitenkin hyödyntää myös yleislaskennassa (CUDA-rajapinta, kurssi Rinnakkaislaskenta grafiikkasuorittimilla). Enemmän prosessoreita voi vuokrata käyttöönsä pilvipalveluista kohtuuhintaan (esim. 32 ydintä parilla eurolla per tunti). 4

Miksi rinnakkaisuuden merkitys kasvaa Mooren lain (1965) mukaan transistorien määrä mikropiirillä kaksinkertaistuu 2 vuodessa. Tämä kehitys jatkuu edelleen. Viime vuosiin asti transistorimäärän kasvu on heijastunut suoraan suorittimien kellotaajuuksin ja siten laskennan nopeuteen. Tämä kehitys on nyt oleellisesti pysähtynyt. Jatkossa transistorien lisääntyminen nopeuttaa laskentaa vain epäsuorasti: moniydinsuorittimet enemmän nopeaa muistia. Johtopäätös: jos halutaan jatkossa yhä nopeampia ohjelmia, niin kuin tähänkin asti, on pakko ruveta hyödyntämään rinnakkaisuutta. 5

Seuraavan sivun kuvan kaksi ylintä käyrää havainnollistavat tilannetta Intelin suorittimien osalta: vihreä/neliöt: transistorien lukumäärä sininen/pallot: kellotaajuus. Pystyakselin asteikko on logaritminen, joten nouseva suora esittää eksponentiaalista kasvua. Kuvassa on myös vaaleansininen/kolmiot: suorittimen teho sinipunainen: suorituskyky kellosykliä kohti (ILP, Instruction Level Parallelism) 6

Herb Sutter. The free lunch is over: a fundamental turn toward concurrency in software. Dr. Dobb s Journal 30(3), March 2005 (kuva päivitetty 8/2009) 7

Paljonko rinnakkaisuudesta voi hyötyä Tarkastellaan esimerkkinä tehtävää valmistaa kattilallinen vihanneskeittoa seuraavalla hieman yksinkertaistetulla algoritmilla: 1. Pilko vihannekset. 2. Laita ainekset kattilaan ja anna kiehua, kunnes keitto on kypsä. Oletetaan, että yhdeltä kokilta vaihe 1 vie A minuuttia ja vaihe 2 B minuuttia. Jos kokkeja on useita, vaihe 1 voidaan rinnakkaistaa, vaihetta 2 ei voi. Saman keiton valmistamiseen menisi K kokin joukkueelta parhaimmillaan A/K + B minuuttia. Siis nopeutus voi olla korkeintaan S = A + B A/K + B. Käytännössä tähän ei yleensä päästä, sillä etenkin suurilla K aiheutuu lisäkuormaa kommunikoinnista ym. 8

Olkoon nyt yleisemmin T (p) laskenta-aika, kun jokin ongelma ratkaistaan käyttäen p suoritinta. Erityisesti T (1) on sarjalliseen ratkaisemisee kuluva aika. Tarkastelemme nopeutusta S(p) = T (1) T (p). Oletetaan, että laskennasta osuus r voidaan rinnakkaistaa. Edellisen sivun tilanteessa siis T (1) = A + B ja r = A/(A + B). Kun käytetään p prosessoria, laskenta-aika on ainakin ja nopeutus korkeintaan S(p) T (p) (1 r)t (1) + rt (1)/p T (1) (1 r)t (1) + rt (1)/p = p (1 r)p + r. Tämä tunnetaan nimellä Amdahlin laki (1967). Kun p, yläraja lähestyy arvoa 1/(1 r). 9

Millaiset algoritmit rinnakkaistuvat Jotta jotain laskentaa voisi merkittävästi nopeuttaa rinnakkaisuudella, sen pitää olla jaettavissa osaongelmiin, jotka ovat ratkaistavissa toisistaan riippumatta missä tahansa järjestyksessä. Tarkastelemme esimerkkinä kolmea Java 8:n metodia, joiden toteutus hyödyntää rinnakkaisuutta: Arrays.parallelSet: taulukon alustaminen Arrays.parallelSort: taulukon järjestäminen Arrays.parallelPrefix: ns. alkuosasummien laskeminen. Ohjelmoijan näkökulmasta näitä metodeja voi käyttää tuntematta rinnakkaisuuden teknistä toteutusta näitä metodeja luultavasti kannattaa käyttää, jos tietää, että laitteisto tarjoaa paljon rinnakkaista laskentakapasiteettia. 10

Arrays.parallelSet( taulukko, funktio ) Jos halutaan esim. alustaa taulukon neliot alkioksi 0, 1, 4, 9, 16,..., tämä tapahtuu kutsulla Arrays.parallelSet(neliot, i -> i*i); Alustusarvot määräävän funktion esitys i -> i*i on lambdalauseke. Meidän kannaltamme se on yksinkertainen tapa ilmaista yksinkertaisia funktioita. Emme tässä mene syvemmälle Javan lambdalausekkeiden filosofiaan. Oletetaan, että alustettavan taulukon koko on n ja yhden alkion alustaminen sujuu vakioajassa. Sarjallisesti aikavaativuus siis on O(n). Jos käytettävissä on p prosessoria, kullekin niistä voi antaa vastuulle noin n/p alkion mittaisen pätkän taulukosta. Kukin prosessori alustaa oman pätkänsä sarjallisesti ajassa O(n/p). Siis laskenta on kokonaisuudessaan rinnakkaistuva, ja saavutamme nopeutuksen p. 11

Taulukon alustaminen on esimerkki ns. nolottavan rinnakkaisesta (embarassingly parallel) ongelmasta. Ongelma rinnakkaistuu maksimaalisen tehokkaasti ilman, että asiaa tarvitsee juuri edes miettiä. Muita tämän tyyppisiä ongelmia ovat esim. monet raakaan voimaan perustuvat etsinnät optimointimenetelmät ja simulaatiot, joissa tehdään useita toistoja satunnaisilla alkuarvoilla. 12

Arrays.parallelSort( taulukko ) Lähtökohtana Javan rinnakkaisjärjestämisessä on lomitusjärjestäminen. Pseudokoodi sivulta 104: merge-sort(a,vasen,oikea) 1 if vasen < oikea 2 keski = (vasen + oikea)/2 3 merge-sort(a,vasen,keski) 4 merge-sort(a,keski+1,oikea) 5 merge(a,vasen,keski,oikea) Selvästi rivien 3 ja 4 rekursiiviset kutsut eivät mitenkään vaikuta toisiinsa ja voidaan suorittaa rinnakkain. Rivin 5 lomituksen rinnakkaistaminen ei ole yhtä helppoa. Normaali lomitusalgoritmi tarkastelee alkioita tiukasti peräkkäin. Jotain voidaan kuitenkin tehdä. Esitämme seuraavassa perusidean siitä, miten lomitus rinnakkaistetaan Javan parallelsortissa. 13

Tarkastellaan lomituksen perustilannetta: Taulukot A[1... n] ja B[1... n] ovat järjestyksessä. Haluamme lomittaa ne taulukkoon C[1... 2n]. Siis taulukkoon C pitäisi tulla taulukoiden A ja B alkiot niin, että se on kokonaan järjestyksessä. Merkitsemme tätä operaatiota merge(a, B, C). Tunnetusti tämän voitaisiin tehdä sarjallisesti ajassa O(n) (sivu 108). Rinnakkaisuuden mahdollistamiseksi sovellamme hajoita ja hallitse -periaatetta ja jaamme taulukon A puoliksi. Siis palautetaan siihen, että suoritetaan merge(a, B, C) merge(a[1... n/2], B[1... x], C[1... n/2 + x]) ja merge(a[n/2 + 1... n], B[x + 1... n], C[n/2 + x + 1... 2n]). Taulukon B jakokohta x valitaan siten, että B[x] < A[n/2] B[x + 1]. Tämä löytyy binäärihaulla. (Lisäksi pitää ottaa huomioon erikoistapaukset, joissa B:n alkiot ovat kaikki suurempia kuin A[n/2] tai kaikki pienempia kuin A[n/2].) 14

Tässä ratkaisussa kokoa 2n oleva ongelma jaetaan osiin, joiden koot ovat n/2 + x ja 3n/2 x. Aikavaativuuden kannalta olisi hyvä, jos jako olisi mahdollisimman tasainen eli x n/2. Tästä ei kuitenkaan ole mitään takeita. Pahimmassa tapauksessa x on taulukon B jommassa kummassa päässä ja osaongelmien koot ovat 3n/n ja n/2. Tämän epätasapainoisempia jakoja ei kuitenkaan pääse syntymään, koska kumpaankin osaongelmaan tulee ainakin n/2 taulukon A alkiota. Algoritmia voi soveltaa, vaikka taulukot eivät olisi saman kokoisia. Tällöin kannattaa valita suurempi taulukko A:n roolin, jolloin taas pienempäänkin osaongelmaan tulee aina vähintään 1/4 kaikista alkioista. 15

Arrays.parallelPrefix( taulukko, binäärioperaattori ) Perusesimerkki tämän metodin käytöstä on alkuosasummien laskeminen. Jos alkutilanteessa taulu on int-taulukko ja alkion taulu[i] arvo on a i, niin kutsun Arrays.parallelPrefix(taulu, (int a, int b) -> a+b)) sijoittaa kaikilla i alkion taulu[i] arvoksi i j=0 a j. Tässä esitämme toisena argumenttina olevan binäärioperaattorin käyttämällä taas lambdalauseketta. Voimme antaa argumentiksi minkä tahansa binäärioperaattorin, joka on liitännäinen eli toteuttaa ehdon a (b c) = (a b) c. Esim. maksimi a b = max(a, b) kelpaa. Suorittamalla Arrays.parallelPrefix(taulu, (int a, int b) -> Math.max(a,b)) alkion taulu[i] arvoksi tulee max { a 0,..., a i }. Monimutkaisempia binäärioperaattoreita voidaan määritellä rajapinnan BinaryOperator avulla. Emme kuitenkaan käsittele tässä asiaa enempää. 16

Alkusumman laskemista voidaan käyttää osana hyvin monenlaisia rinnakkaisia algoritmeja. Esimerkki Reikäkorttijärjestämisessä (radix sort) eräs vaihe on järjestellä taulukon luvut siten, että parilliset luvut tulevat ennen parittomia. Parillisten lukujen keskinäinen järjestys säilyy, samoin parittomien. Esim. taulukosta tulee [27, 8, 5, 12, 4, 17, 13, 16] [8, 12, 4, 16, 27, 5, 17, 13]. Alkion A[i] uusi sijainti voidaan laskea alkuosasummaa soveltamalla. 17

Tehdään tämä järjestämisaskel taulukolle taulu[0... n-1]. 1. Merkitään aputaulukkoon paikka ykkönen parittomien alkioiden kohdalle: Arrays.parallelSet(parittomia, i -> taulu[i]%2); 2. Suoritetaan Arrays.parallelPrefix(parittomia, (int a, int b) -> a+b); minkä jälkeen parittomia[i] kertoo parittomien lukujen määrän osataulukossa taulu[0... i]. Erityisesti koko taulukon parillisten lukujen määrä on n-parittomia[n-1]. 3. Jos taulu[i] on parillinen, sen uusi paikka on i-parittomia[i]. Jos taulu[i] on pariton, sen uusi paikka on (n-parittomia[n-1])+parittomia[i]-1. 18

Miksi alkuosasummien laskeminen sitten on rinnakkaistuvaa? Tarkastellaan esimerkkinä 8-alkioista taulukkoa A[1... 8]. Taulukon koko summan A[1] +... + A[8] laskeminen voidaan rinnakkaistaa seuraavasti: vaiheessa 1 lasketaan rinnakkain A[2] = A[1] + A[2], A[4] = A[3] + A[4], A[6] = A[5] + A[6] ja A[8] = A[8] + A[7]. vaiheessa 2 lasketaan rinnakkain A[4] = A[4] + A[2] ja A[8] = A[8] + A[6]. vaiheessa 3 lasketaan A[8] = A[8] + A[4]. 19

Yleisemmin n alkion summan laskemisessa vaiheita tulee log n. Kukin vaihe menee vakioajassa, jos käytössä on ainakin n prosessoria. Jos prosessorien lukumäärä on p < n, voidaan jakaa taulukko ensin p osataulukkoon, joiden koko on n/p. (Oletetaan yksinkertaisuuden vuoksi, että n ja p ovat kakkosen potensseja.) Ensin kukin prosessori summaa oman osataulukkonsa sen kokoon verrannollisessa ajassa O(n/p). Sen jälkeen saadut p osasummaa voidaan laskea yhteen edellisen sivun menettelyllä ajassa O(log p). Aikavaativuus koko taulukon summan laskemiselle on siis O(n/p + log p). Jos n p log p, niin termi O(n/p) dominoi, joten p prosessoria antaa melkein nopeutuksen p. Samalla taulukkoon tulee muita osasummia, joita sopivasti yhdistelemällä saadaan lasketuksi muutkin alkuosasumman alkiot. 20

Kaikkien osasummien laskeminen taulukossa A[1... n] on helpointa esittää rekursiivisesti. (Oletetaan taas, että n on kakkosen potenssi.) 1. Jos n = 1, palaa aliohjelmasta tekemättä mitään. 2. Muodosta aputaulukko B[1... n/2]. 3. Laske rinnakkain B[i] = A[2i] + A[2i 1] kun i = 1,..., n/2. 4. Laske rekursiivisesti taulukon B alkusummat. 5. Laske rinnakkain A[2i] = B[i] ja A[2i 1] = B[i 1] + A[2i 1] kun i = 1,..., n/2. Aikavaativuudeksi nähdään O(n/p + log p) samaan tapaan kuin edellä. Lisää alkuosasumman laskemisesta ja soveltamisesta löytyy esim. Guy E. Blellochin artikkelista https://www.cs.cmu.edu/~guyb/papers/ble93.pdf. 21

Esimerkki: Primin algoritmi Esimerkkinä rinnakkaisuuden soveltamisessa osana monimutkaisempaa algoritmia tarkastelemme Primin algoritmia pienimmälle virittävälle puulle tiheässä verkossa. Lähdetää liikkeelle sarjallista algoritmista (s. 568): Prim-Dense(G,w,r) 1 S = {r} 2 distance[r] = 0 3 for kaikille solmuille v V \ {r} 4 distance[v] = w(r,v) // ääretön, jos kaari puuttuu 5 parent[v] = r 6 while S V 7 valitse u V \ S, jolla distance[u] on pienin 8 T = T {(parent[u],u)} 9 for jokaiselle solmulle v vierus[u] 10 if v S and w(u,v) < distance[v] 11 parent[v] = u 12 distance[v] = w(u,v) 13 return T 22

Kuten muistetaan, sarjallisen algoritmin aikavaativuus on O(n 2 ), missä n = V on solmujen lukumäärä: rivit 1 2 menevät vakioajassa rivin 3 for-silmukka suoritetaan n kertaa, ja silmukan runko riveillä 4 5 vie vakioajan rivin 6 while-silmukka suoritetaan n 1 kertaa rivillä 7 pienin alkio etsitään ajassa n rivin 9 silmukka suoritetaan n kertaa, ja runko vie vakioajan. Aikavaativuutta siis dominoivat pienimmän alkion etsiminen rivillä 7 ja sisempi silmukka rivillä 9. 23

Oletetaan nyt, että käytettävissä on p suoritinta, missä p n. Oletetaan yksinkertaisuuden vuoksi, että n/p on kokonaisluku. Pienimmän alkion etsiminen taulukosta distance voidaan rinnakkaistaa seuraavasti: 1. Jaetaan taulukko distance p osataulukkoon, joiden koko on n/p. Varataan aputaulukko apu[1... p]. Suoritin numero i saa vastuulleen osataulukon numero i, eli distance[(i 1)n/p + 1... in/p], sekä alkion apu[i]. 2. Kukin suoritin i toisistaan riippumatta etsii oma osataulukkonsa pienimmän alkion ja sijoittaa sen paikkaan apu[i]. Tämä vie ajan O(n/p). 3. Lasketaan arvoksi apu[p] koko taulukon apu[1... p] pienin arvo. Tämä tapahtuu ajassa O(log p) samaan tapaan kuin alkuosasummien laskemisessa. Pidetään samalla kirjaa, millä indeksillä u minimi löytyi. 4. Välitetään globaali minimi apu[p] ja indeksi u kaikille suorittimille. Tämä tapahtuu ajassa O(log p): alussa vain suoritin numero p tietää arvot laskenta-askelessa k tieto on jo levinnyt 2 k suorittimelle, ja kukin niistä kertoo sen yhdelle uudelle suorittimelle. 24

Rivien 9 12 silmukka voidaan samoin suorittaa ajassa O(n/p) yksinkertaisesti antamalla kunkin suorittimen vastuulle n/p solmua. Kun muistetaan, että edellä analysoidut osat suoritetaan n 1 kertaa, saadaan kokonaisaikavaativuudeksi O(n 2 /p + n log p). Jos p log p n, termi n 2 /p dominoi, ja saavutamme jälleen lähes optimaalisen nopeutuksen p. Huom. Tällaisiin O-arvioihin on syytä suhtautua varauksella: Käytännössä p on usein melko pieni. Tällöin O-merkinnän piilottamat vakiokertoimet tulevat merkitseviksi. Yleensä rinnakkaisen ratkaisun vakiokertoimet ovat suuremmat. Suurilla p suorittimien välinen kommunikointi voi muodostua pullonkaulaksi. Tämä pätee etenkin, jos laskenta hajautetaan usealle eri tietokoneelle. Suoritusaika saattaa kääntyä jopa nousuun, jos suorittimia lisätään liikaa. 25

Yhteenveto Tehokkaassa ohjelmoinnissa pitää algoritmien teoreettisen aikavaativuuden lisäksi ymmärtää, millaisessa ympäristössä ohjelma suoritetaan. Nykyaikaisissa laskentaympäristöissä rinnakkaisuus on erittäin tärkeä tapa lisätä laskentatehoa, ja sen merkitys ei ainakaan vähene lähitulevaisuudessa. Jos tietää perusoperaatioita, jotka rinnakkaistuvat hyvin, niitä voi käyttää osana ohjelmia murehtimatta rinnakkaisuuden toteutuksesta. Rinnakkaisuutta ja samanaikaisuutta käsitellään enemmän muilla kursseilla (esim. Tietokoneen toiminta ja Käyttöjärjestelmät). 26