Rinnakkaisuuden hyväksikäyttö tietokonepeleissä Paula Kemppi Helsinki 1.5.2008 Ohjelmistotuotanto ja tietokonepelit -seminaari HELSINGIN YLIOPISTO Tietojenkäsittelytieteen laitos
Sisältö i 1 Johdanto 1 2 Rinnakkaisuus 1 2.1 Pelimoottorit............................... 2 2.2 Amdahlin laki............................... 2 3 Pelimoottorien rinnakkaisuuden mallit 3 3.1 Synkroninen funktionaalinen rinnakkaisuus............... 3 3.2 Asynkroninen funktionaalinen rinnakkaisuus.............. 4 3.3 Rinnakkaisen tiedon malli........................ 5 4 Tehtävien väliset riippuvuudet 7 4.1 Rinnakkaisuuden ehtoja......................... 7 5 OpenMP 8 6 Yhteenveto 11 Lähteet 12
1 Johdanto 1 Viime vuosina on prosessorien suorituskyvyn kasvulle alkanut tulla raja vastaan, sillä kellotaajuutta ei voida enää nostaa aiemmin totuttuun tapaan. Kun aiemmin ohjelmistoja ovat rajoittaneet esimerkiksi internetyhteyden nopeus tai näytönohjaimen taso, alkaa tulevaisuudessa prosessoritehosta tulla tällainen rajoittava tekijä [Sut05]. Näin ollen prosessoritehoja pitää pystyä hyödyntämään entistä paremmin. Koska yksittäisten prosessorien tehoja ei voi enää rajattomasti kasvattaa, ovat prosessorien valmistajat alkaneet hakea lisätehoja monisäikeisyydestä (hyperthreading) ja moniydinarkkitehtuureista (multicore architectures) [Sut05]. Mutta jotta moniydinprosessoreista saataisiin paras hyöty irti, pitää ohjelmistoihinkin lisätä rinnakkaisuutta. Muuten käy helposti niin, että osa prosessoreista vaan odottelee joutilaina. Aiemmin monisäieteknologiaa ei ole juurikaan käytetty hyväksi pelimoottorien säikeyttämisessä sen monimutkaisuuden takia, vaikka edut suorituskykyyn ovat olleet tiedossa [And07]. Modernit tietokonepelit kuitenkin koettelevat nykyisten kotitietokoneiden rajoja, joten pelikehittäjien voi olettaa olevan ensimmäisten joukossa ottamassa käyttöön moniydinteknologiaa [GL05]. Tässä artikkelissa käsitellään ensin lyhyesti rinnakkaisuutta ja pelimoottoreita, minkä jälkeen tutustutaan korkean tason malleihin, joilla pelimoottoreita voi toteuttaa rinnakkaisesti. Sitten käsitellään vielä tarkemmin ehtoja, jotka määrittävät voiko tietyt tehtävät suorittaa rinnakkain, ja lopuksi esitellään rinnakkaisohjelmoinnin rajapinta OpenMP. 2 Rinnakkaisuus Rinnakkaisuus tarkoittaa useamman kuin yhden tehtävän suorittamista samanaikaisesti. Jos käytössä on vain yksi prosessori, voidaan rinnakkaisuutta toteuttaa moniajolla (multitasking), jossa rinnakkain suoritettavat tehtävät jakavat prosessorin ja suoritettavaa tehtävää vaihdetaan hyvin nopeasti. Jos taas käytössä on useampi prosessori, voidaan ajaa yhtä montaa prosessia kuin on prosessoreita. Rinnakkaisohjelmointi on siis ohjelman jakamista useisiin rinnakkain suoritettaviin osiin. Peräkkäisohjelmassa puolestaan kaikki tehtävät suoritetaan peräkkäisessä järjestyksessä. Rinnakkaisohjelmoinnin tehtäviin kuuluu ongelman jakaminen rinnakkain suoritettaviin osiin, rinnakkaisten osien kommunikoinnin ja synkronisoinnin suunnittelu ja toisiinsa läheisesti liittyvien osien yhdistäminen [Fos95]. Etenkin olemassa olevaa
ohjelmaa muuttaessa on tärkeää tunnistaa, mitä osia voidaan suorittaa rinnakkain, ja pitää myös pystyä jakamaan ohjelma tällaisiin osiin. 2 2.1 Pelimoottorit Pelimoottorit ovat erikoistuneita väliohjelmistoja, jotka toimivat käyttöjärjestelmän ajurien ja pelin toimintalogiikan välissä. Ne nopeuttavat pelikehitystä ja pienentävät pelin tekemisen riskiä, sillä tekniset ratkaisut voidaan erottaa pelimoottoriin jolloin niitä voidaan käyttää useissa peleissä. Pelimoottorit perustuvat useimmiten erilaisten komponenttien käyttöön. Tällaisia komponentteja ovat esimerkiksi fysiikka-, ääni-, verkko- ja grafiikkakomponentit. Tähän asti pelimoottorit on pitkälti optimoitu hyödyntämään mahdollisimman paljon yksiprosessorisen laitteiston tehoja, sillä pelisilmukan tehtävät ovat perinteisesti olleet peräkkäin suoritettavia. Viime vuosina kaksi- ja moniprosessoriset arkkitehtuurit ovat alkaneet yleistyä ja myös uusimmissa pelikonsoleissa on useita prosessoreita. Nyt pelimoottorien suunnittelijoiden täytyy ottaa entistä enemmän huomioon myös rinnakkaisuus, jos halutaan hyödyntää myös moniprosessoristen laitteistojen tehoja mahdollisimman hyvin. Rinnakkaisuuden lisääminen olemassa olevaan pelimoottoriin ei ole kuitenkaan mikään triviaali muutos, sillä suoritettavien tehtävien välillä on usein riippuvuuksia. 2.2 Amdahlin laki Amdahlin laki on helppo kaava laskea algoritmin tehonlisäys, jos osa siitä muutetaan rinnakkaiseksi. Amdahlin laki esittää perimmäisen rajan sille, paljonko on mahdollista nopeuttaa ohjelmiston suoritusta rinnakkaisuuden avulla. Rinnakkaisuudesta saatavaa nopeutusta rajoittaa peräkkäisen laskennan määrä. Nopeutuminen lasketaan kaavalla missä S = 1 (F + (1 F)/N) S = Rinnakkain suoritettavan algoritmin nopeuden suhde peräkkäin suoritettavaan algoritmiin F = Kuinka suurta osaa algoritmista ei voida suorittaa rinnakkain N = Prosessorien määrä
3 Kuva 1: Synkroninen funktionaalinen malli [Mön06]. Esimerkiksi, jos 50 % suorituksesta on rinnakkaista, on kahdella prosessorilla suoritus 1,3-kertaista verrattuna peräkkäissuoritukseen. Jos F eli peräkkäisen laskennan määrä lähestyy nollaa, lähestyy S N:ää eli algoritmi nopeutuu suoraan suhteessa käytössä olevien prosessorien määrään. 3 Pelimoottorien rinnakkaisuuden mallit On olemassa kaksi perusmallia jakaa ohjelma rinnakkaisiin osiin. Funktionaalisen rinnakkaisuuden mallissa ohjelma jaetaan tehtäviin, joita suoritetaan eri säikeissä. Rinnakkaisen tiedon mallissa puolestaan yritetään löytää tietoa, jolle voi suorittaa samoja tehtäviä rinnakkaisesti. Funktionaalinen rinnakkaisuus voidaan vielä jakaa synkroniseen ja asynkroniseen malliin. Peleissä funktionaalista rinnakkaisuuteen sopivia osa-alueita ovat esimerkiksi tekoäly, ääni, fysiikka, käyttöliittymä ja päivitys ja rinnakkaisen tiedon malliin sopivia tilanteita ovat muun muassa animointi, reitinetsintä, äänen prosessointi ja tietyt tekoälyn algoritmit [Dam07]. 3.1 Synkroninen funktionaalinen rinnakkaisuus Synkronisessa funktionaalisen rinnakkaisuuden mallissa lisätään rinnakkaisuutta yrittämällä löytää olemassa olevasta pelisilmukasta rinnakkaisia tehtäviä. Näiden tehtävien pitäisi mieluiten olla täysin riippumattomia toisistaan. Kuvassa 1 on pelisilmukka, jossa animaatio ja fysiikan laskenta suoritetaan rinnakkain [Mön06]. El Rhalibi et al. ovat kehitelleet kehystä, jolla automatisoida tehtävien jakaminen
4 Kuva 2: Asynkroninen funktionaalinen malli [Mön06]. prosessoreille. Ideana on jakaa toiminnallisuus pieniin tehtäviin, sen jälkeen muodostetaan kaavio, josta nähdään, mitkä tehtävät seuraavat toisiaan, minkä jälkeen tämä riippuvuuskaavio syötetään kehykselle. Kehys osaa aikatauluttaa tehtävien suorituksen ja jakaa ne säikeille ottaen huomioon käytettävissä olevan prosessorien määrän. Funktionaalisten mallien ongelmana on yleensä se, että ne voivat tukea vain rajallista määrää prosessoreja. Synkronisessa funktionaalisen rinnakkaisuuden mallissa rajoitteena on lisäksi se, että tehtävien pitäisi olla toisistaan mahdollisimman riippumattomia. Mallin odotettavissa oleva suoritusaika voidaan nähdä suoraan pelisilmukan pisimmän polun suoritusajasta. Polun pituus liittyy suoraan siihen, kuinka paljon rinnakkaisuutta silmukassa on. Mallin etuna on, että koska komponenttien pitää olla mahdollisimman riippumattomia toisistaan, ei olemassaolevia komponentteja yleensä tarvitse muuttaa paljoa. 3.2 Asynkroninen funktionaalinen rinnakkaisuus Gabb ja Lake [GL05] esittävät vaihtoehtoisen mallin eli asynkronisen funktionaalisen rinnakkaisuuden mallin. Tässä mallissa olennaista on, ettei se sisällä pelisilmukkaa, vaan peliä eteenpäinajavat tehtävät päivittyvät omaa tahtiaan ja käyttävät viimeisintä saatavilla olevaa tietoa. Tällä tavoin saa tehokkaasti toisistaan riippuvat tehtävät rinnakkaisiksi.
5 Myös asynkronisen mallin skaalautuvuutta rajoittaa se, montako tehtävää pelimoottorista voidaan löytää. Koska säikeiden välinen kommunikaatio tapahtuu viimeisintä saatavilla olevaa tietoa käyttäen, ei tehtävien tässä mallissa tarvitse olla toisistaan riippumattomia. Tämän vuoksi asynkroninen malli voi tukea suurempaa määrää tehtäviä ja täten myös suurempaa määrää prosessoreita kuin synkroninen malli. Tässä mallissa tehtävien välisessä kommunikaatiossa ongelmaksi muodostuu oikea ajoitus. Optimaalisessa tilanteessa tietoa päivittävä tehtävä lopettaa suorituksensa juuri, ennen kuin sitä tarvitseva tehtävä aloittaa suorituksensa. Huonolla tuurilla tietoa tarvitseva tehtävä aloittaa juuri, ennen kuin sitä päivittävä tehtävä lopettaa suorituksensa. Jos kyseessä olisi esimerkiksi käyttäjän syötteitä lukeva tehtävä ja näyttöä päivittävä tehtävä, kestäisi jälkimmäisessä tilanteessa lähes kaksinkertainen aika siihen, että näyttö päivittyy syötteiden perusteella, verrattuna optimaaliseen tilanteeseen [Mön06]. Gabb ja Lake ehdottavatkin, että joidenkin tehtävien suoritus kannattaa kalibroida tapahtumaan useammin kuin toisten. Tämä vähentää ongelman merkittävyyttä, mutta ei poista sitä kokonaan. Tässä mallissa suorituskyky ei riipu yhtä paljoa ohjelman peräkkäin suoritettavista osista, koska malli ei vaadi juurikaan synkronisointia rinnakkaisten tehtävien välille. Näin ollen suorituskykyä rajoittaa lähinnä se, voidaanko löytää tarpeeksi rinnakkaisia tehtäviä, sillä tehtävien pitäisi olla hyvin tasapainossa. Asynkronisessa mallissa joudutaan olemassa olevia komponentteja todennäköisesti muuttamaan, että ne toimivat tilanteessa, jossa käytetään viimeisintä saatavilla olevaa tietoa komponenttien välisessä kommunikaatiossa. Tarvitaan lisäksi säieturvallinen tapa tiedustella tilojen viimeisimpiä päivityksiä. Näiden muutosten toteuttamisen ei kuitenkaan pitäisi olla vaikeaa. 3.3 Rinnakkaisen tiedon malli Rinnakkaisen tiedon mallissa etsitään tietoa, jolle voi suorittaa samoja tehtäviä rinnakkain. Pelimoottoreissa tämä tarkoittaa yleensä pelin hahmoja. Kuvassa 3 on esimerkki, jossa käytössä on kaksi säiettä. Näistä toinen hoitaa puolet hahmoista ja toinen puolet. Optimaalisessa tilanteessa pelimoottori käyttää yhtä monta säiettä kuin on prosessoreita käytettävissä. Mallissa olennaista on, miten hahmot jaetaan säikeille, sillä säikeiden pitäisi olla mahdollisimman tasapainossa, että laskenta jakautuu tasaisesti kaikille prosessoreille. Pitää ottaa huomioon myös, miten eri säikeissä olevien hahmojen kommunikaatio
6 Kuva 3: Rinnakkaisen tiedon malli [Mön06]. hoidetaan. Synkronoitu kommunikaatio vähentäisi rinnakkaisuutta, joten suositeltavampaa on käyttää asynkronisen mallin tapaan viimeisimpiä päivityksiä viestinvälityksessä [Mön06]. Säikeiden välistä kommunikaatiota voi myös vähentää sijoittamalla samaan säikeeseen hahmot, jotka todennäköisimmin kommunikoivat keskenään. Koska hahmot useimmiten kommunikoivat naapuriensa kanssa, yksi vaihtoehto olisi ryhmittää hahmot sijainnin mukaan. Rinnakkaisen tiedon malli on erittäin skaalautuva, koska säikeiden määrä voidaan automaattisesti valita sen mukaan, montako prosessoria järjestelmässä on, ja ainoat osat pelisilmukassa, joita ei voi suorittaa rinnakkaisesta, ovat ne, jotka eivät ole suoraan tekemisissä pelin hahmojen kanssa. Funktionaalisen rinnakkaisuuden mallit pystyvät paremmin hyödyntämään prosessoritehoja silloin, kun käytettävissä on korkeintaan muutama prosessori, mutta rinnakkaisen tiedon mallia tarvitaan, jos halutaan kunnolla hyödyntää tulevaisuuden prosessorien kymmeniä ytimiä [Mön06]. Rinnakkaisen tiedon mallin tehokkuus riippuu suoraan siitä, kuinka suuri osa pelimoottorista saadaan rinnakkaiseksi tiedon suhteen. Jos pelimoottori pystyy hyödyntämään rinnakkaisen tiedon mallia suurimmalle osalle pelisilmukasta, on tämä mallin suorituskyky näistä kolmesta mallista paras. Suurin puute on, että tarvitaan komponentteja, jotka tukevat tiedon rinnakkaisuutta. Joillekin komponenteille pitää esimerkiksi pystyä tekemään useita päivityksiä rinnakkaisesti.
4 Tehtävien väliset riippuvuudet 7 Ideaalisessa rinnakkaisessa ohjelmassa on toisistaan täysin riippumattomia tehtäviä, jotka voidaan suorittaa kaikki samaan aikaan. Useimmiten osa tehtävistä tarvitsee toisten tehtävien tarjoamaa tietoa, joten niiden täytyy odottaa näiden toisten tehtävien suorituksen päättymistä. Tehtäväriippuvuuskaaviolla voidaan ilmaista tällaisia tehtävien välisiä riippuvuuksia ja niiden suoritusjärjestystä suhteessa toisiinsa. Riippuvuusanalyysillä pyritään löytämään ne tehtävät, joita ei voi suorittaa rinnakkain, ja Bernsteinin ehdoilla puolestaan voidaan tunnistaa sellaiset tehtävät, jotka voidaan suorittaa rinnakkain. 4.1 Rinnakkaisuuden ehtoja Yksi keskeisistä riippuvuuksista on tietoriippuvuus, joka voidaan jakaa viiteen alikategoriaan [REC05]: Kahden tehtävän S1 ja S2 välillä on suoritusriippuvuus, jos niiden välillä on suorituspolku ja vähintään yksi tehtävän S1 tulosteista toimii tehtävän S2 syötteenä Tehtävien S1 ja S2 välillä on antiriippuvuus, jos S2 seuraa suorituksessa tehtävää S1 ja S2:sen tulosteet vaikuttavat tietoon, jota tehtävä S1 käyttää syötteenään Tulosteriippuvuus on silloin, jos tehtävät S1 ja S2 kirjoittavat samaan muistiosoitteeseen. I/O riippuvuus on silloin, jos kaksi tehtävää käyttää samaa tiedostoa. Tuntematon riippuvuus tarkoittaa sitä, että tehtävien suhteet riippuvat tiedosta, johon viitataan epäsuorasti Hallintariippuvuus on oma riippuvuuskategoriansa, joka estää rinnakkaisuuden komentojen välillä. Bernsteinin ehdot ovat kategorialtaan resurssiriippuvuutta, koska ne käsittelevät prosesseja niiden muistiosoitteiden suhteen, joista ne lukevat ja joihin ne kirjoittavat. Ehdot ovat: Prosessin P1 lukemien muistiosoitteiden ja prosessin P2 muokkaamien muistiosoitteiden leikkauksen pitää olla tyhjä
8 Prosessin P2 lukemien muistiosoitteiden ja prosessin P1 muokkaamien muistiosoitteiden leikkauksen pitää olla tyhjä Prosessin P1 muokkaamien muistiosoitteiden ja prosessin P2 muokkaamien muistiosoitteiden leikkauksen pitää olla tyhjä Analysoimalla tietoriippuvuutta, hallintariippuvuutta ja resurssiriippuvuutta voidaan tunnistaa rinnakkaisuuden mahdollisuudet. El Rhalibi et al. [REC05] ovat kehittäneet rinnakkaisen peliohjelmoinnin kehyksen (Concurrent Game Programming Framework), jolla voi mallintaa pelejä syklisinä tehtäväriippuvaisina kaavioina ja aikatauluttajan, jolla suorittaa pelien tehtävät skaalattavassa moniydinprosessoriarkkitehtuurissa. Jotta aiemmin täysin peräkkäissuoritettavasta pelisilmukasta voidaan tunnistaa rinnakkaiset osiot, tarvitaan käsiteltyjä rinnakkaisuuden malleja. Kun niiden avulla on määritelty kunkin tehtävän riippuvuussuhteet, voidaan pelin tehtävistä muodostaa syklinen riippuvuuskaavio. Tämän jälkeen he toteuttivat kaavion mukaisesti suoritettavan pelin rinnakkaisen peliohjelmoinnin kehyksessä. He testasivat kehyksen suorituskykyä sekä kaksi- että neliprosessorisella järjestelmällä ja havaitsivat, että riippuvuuskaaviomalli vaatii vähintää kolme prosessoria, ennen kuin siltä voidaan olettaa parempaa suorituskykyä kuin vastaavalta peräkkäissilmukassa suoritettavalta ohjelmalta [REC05]. 5 OpenMP OpenMP (Open Multi-Processing) on ohjelmointirajapinta, joka tukee jaettua muistia käyttävää rinnakkaisohjelmointia useilla eri alustoilla, kuten Windows ja Unix. Se sisältää kääntäjädirektiivejä, kirjastorutiineja ja ympäristomuuttujia, jotka vaikuttavat suoritusaikaiseen käyttäytymiseen. C/C++ tai Fortran-kieliseen ohjelmaan lisätään kommentteja muistuttavia pragmoja eli kääntäjädirektiivejä, jotka ohjaavat työnjakoa säikeiden kesken ja vaikuttavat muuttujien näkyvyyteen. Jos halutaan esimerkiksi jakaa kaikkia hahmoja vuorollaan päivittävän metodin suoritus useille prosessoreille, riittää OpenMP:ssä yhden koodirivin lisääminen for-silmukan eteen, kun muuten jouduttaisiin mahdollisesti tekemään oma for-silmukka jokaiselle prosessorille. #pragma omp parallel for for (int i = 0; i < numparticles; i++) UpdateParticles(particle[i]);
9 Kuva 4: Säiejoukon käyttö OpenMP:ssä [Bar] OpenMP:tä tukeva kääntäjä luo koodia, joka automaattisesti jakaa silmukan useiksi rinnakkaisiksi osioiksi, jotka suoritetaan riippumatta toisistaan. Osioiden määrä riippuu prosessorien määrästä ja ohjelmoijan tekemistä määrityksistä. Kun yllä olevaa silmukkaa testattiin käyttämällä olioiden määränä sataatuhatta ja antamalla päivitykselle tietty standardiaika, havaittiin tämän silmukan suorituksen tehostuneen OpenMP:tä käyttämällä lähes kolminkertaiseksi kaksiytimisessä Pentiumissa ja lähes viisinkertaiseksi kolmiyritimisessä Xbox 360:ssa [Ise06]. Suorituskyky kasvoi käytännössä yhtä paljon kuin käyttämällä Windowsin säiekutsuja ja synkronisointimäärityksiä, jotka kuitenkin vaativat yli 60 koodiriviä OpenMP:n yhteen verrattuna. OpenMP:tä voi hyödyntää pelissä esimerkiksi törmäyksentunnistussilmukassa. Koska silmukan suorituksen kesto vaihtelee riippuen siitä, onko hahmo törmäämässä vai ei, voidaan käyttää dynaamista aikataulutusta, jolloin kääntäjä aikatauluttaa säiejoukkion vasta suoritusaikana. OpenMP:tä käyttäen saatiin huomattavaa parannusta suorituskykyyn verrattuna alkuperäiseen sarjallisesti suoritettavaan silmukkaan [Ise06]. OpenMP:n etuna on, että tekniikka on täysin alustariippumatonta, sillä jos kääntäjä ei tue OpenMP:tä, se vain jättää #pragma-alkuisen rivin huomiotta. Jos taas kääntäjä tukee OpenMP:tä, lisää se automaattisesti kohdearkkitehtuurin vaatimat rinnakkaisuuden rakenteet [Ise06]. Jos alustalla ei olekaan useita prosessoreita, jää OpenMP:n aiheuttama lisärasite todennäköisesti hyvin pieneksi [Ise06]. Lisäksi koodi on huomattavasti helppolukuisempaa, kuin normaali monisäikeinen koodi. OpenMP:n idea perustuu säiejoukkoon. Kun ohjelman suoritus tulee rinnakkaiseen osioon, säiejoukko aktivoituu ja suoritettuaan työnsä, jäävät odottamaan seuraavaa rinnakkaista osiota. Koska säikeiden luonti kuluttaa resursseja, luodaan useimmissa OpenMP:n toteutuksissa säikeet ensimmäisellä käyttökerralla, minkä jälkeen niitä uudelleenkäytetään koko ohjelman suorituksen ajan. Kuvassa 4 nähdään, miten pääsäie herättää rinnakkaisen osion alkaessa säiejoukon, joka on aktiivisena vain rinnakkaisen osion ajan.
10 Useimmiten OpenMP:llä pyritään saamaan tieto rinnakkaiseksi, kuten silmukkaesimerkissä, mutta sitä voidaan käyttää myös funktionaaliseen rinnakkaisuuteen. Funktioiden jakaminen kahteen säikeeseen onnistuu helposti seuraavalla koodilla: #pragma omp parallel sections { #pragma omp section { Function1(); Function2(); } #pragma omp section { Function3(); Function4(); } } //tässä odotetaan kaikkien säikeiden suorituksen päättymistä Tässä ratkaisussa osioiden määrä kuitenkin rajoittaa luotavien säikeiden määrää, joten kaikkia prosessoreita ei välttämättä päästä hyödyntämään, ja jos prosessoreita on vähemmän kuin osioita, päättää OpenMP:n aikatauluttaja, missä järjestyksessä osiot suoritetaan [And07]. Esimerkiksi Intelin C++-kääntäjä sallii tehtävien laittamisen jonoon säiealtaaseen. Tällöin on helpompi hyödyntää kaikkia järjestelmän prosessoreita. Alla olevassa koodiesimerkissä näkee, miten kukin funktio voidaan asettaa jonoon. Prosessorin vapautuessa pääsee jonossa seuraavana oleva tehtävä suoritukseen. Pitää kuitenkin hyvin miettiä, miten tehtävät jonoon asettaaa, sillä säikeiden määrä ei ole välttämättä ennalta tiedossa. #pragma omp parallel sections { #pragma intel omp task Function1(); #pragma intel omp task Function2(); #pragma intel omp task Function3(); #pragma intel omp task Function4(); } OpenMP:n huonona puolena voi mainita sen, ettei kääntäjä tarkista, toimiiko koodi rinnakkaisena niinkuin pitäisi. Rinnakkaiset osiot voidaan suorittaa missä järjestyk-
11 sessä tahansa, joten ohjelmoijan pitää pitää huoli, että OpenMP:tä käytetään vain sellaisille rakenteille, jotka eivät ole riippuvaisia suoritusjärjestyksestä. Tämän lisäksi myös virheidenetsintä saattaa olla erittäin vaikeaa, koska OpenMP:n sisäiseen toimintaan ei suoritusaikana pääse käsiksi [Ise06]. 6 Yhteenveto Koska prosessorien kellotustaajuuden kasvaminen on melko lailla pysähtynyt, täytyy lisätehoja hankkia muilla keinoin. Tämän vuoksi moniydinprosessorit ovat alkaneet yleistyä niin tietokoneissa kuin pelikonsoleissakin. Prosessorien tehoa jää kuitenkin helposti hyödyntämättä, jos ohjelmistokehityksessä ei ole otettu rinnakkaisuutta huomioon. Useista prosessoreista saatava hyöty on riippuvainen siitä, kuinka suuri osa ohjelman tehtävistä voidaan suorittaa rinnakkain. On olemassa kaksi perusmallia jakaa ohjelmistot rinnakkain suoritettaviksi tehtäviksi: funktionaalisen riippuvuuden malli ja riippuvaisen tiedon malli. Funktionaalinen voidaan lisäksi jakaa synkroniseen ja asynkroniseen malliin. Synkronisessa voidaan suorittaa vaikkapa tekoälytehtävää ja fysiikkatehtävää rinnakkain, mutta tässä mallissa pelisilmukka muistuttaa edelleen suurelta osin perinteistä pelisilmukkaa. Asynkronisessa mallissa puolestaan tehtäviä suoritetaan omaa tahtiaan omissa säikeissään ja jos tehtävä tarvitsee tietoa toiselta tehtävältä, käyttää se viimeisintä saatavilla olevaa tietoa. Riippuvaisen tiedon mallissa puolestaan jaetaan pelistä tiettyjen kohteiden, jotka käsittelevät samaa tietoa, suoritus useisiin säikeisiin. Rinnakkaisohjelmoinnissa on olennaista ottaa huomioon tehtävien väliset riippuvuudet. Tällaisia riippuvuuksia on esimerkiksi, että tehtävä tarvitsee toisen tehtävän tuottamaa tietoa tai että kaksi tehtävää tallentaa tietoa samaan muistiosoitteeseen. Bernsteinin ehdot tarkastavat, etteivät tehtävät käsittele samoja muistiosoitteita. OpenMP on helppo ja nopea tapa hyödyntää moniydinprosessoreita. Vain yhdellä koodirivilla saa esimerkiksi muutettua silmukan suoritusta niin, että se jaetaankin useisiin osioihin, jotka suoritetaan rinnakkain toisistaan riippumatta. Huonona puolena mainittakoon, että kääntäjä ei juurikaan tarkasta, toimiiko koodi oikein rinnakkaisena. Peleissä OpenMP:tä voi hyödyntää muun muassa törmäyksen havaitsemisessa, reitinetsinnässä, simulaatioissa ja signaalinkäsittelyssä.
Lähteet 12 And07 Bar Dam07 Eld06 Fos95 Andrews, J., Threading basics for games. Tekninen raportti, Intel R Software Network, 2007. URL http://softwarecommunity.intel. com/articles/eng/2601.htm. [14.4.2008]. Barney, B., Openmp tutorial. URL https://computing.llnl.gov/ tutorials/openmp/. Damon, W., Multithreaded game programming and hyper-threading technology. Tekninen raportti, Intel R Software Network, 2007. URL http://softwarecommunity.intel.com/articles/eng/3351. htm. [14.4.2008]. Eldawy, M., Game programming in a multi core world. Tekninen raportti, Academic ADL Co-Lab, 2006. URL adlcommunity.net/file.php/ 23/GrooveFiles/Games%20Madison/report%20Multithreading.pdf. Foster, I., Designing and building parallel programs. Addison-Wesley Reading, Mass, 1995. GL05 Gabb, H. ja Lake, A., Threading 3d game engine basics, 2005. URL http://www.gamasutra.com/features/20051117/gabb_ 01.shtml. [14.4.2008]. HM Ise06 Harvey, M. ja Marshall, C., Scheduling Game Events. Game Programming Gems, 3, sivut 5 14. Isensee, P., Game Programming Gems, Vol. 6, chapter Utilizing Multicore Processors with OpenMP. Charles River Media. Mön06 Mönkkönen, V., Multithreaded game engine architectures, 2006. URL http://www.gamasutra.com/features/20060906/monkkonen_ 01.shtml. [14.4.2008]. REC05 Rhalibi, A. E., England, D. ja Costa, S., Game engineering for a multiprocessor architecture. Changing Views: Worlds in Play, de Castell Suzanne ja Jennifer, J., toimittajat, Vancouver, June 2005, University of Vancouver, sivu 12, URL http://www.digra.org/dl/display_ html?chid=06278.34239.pdf.
13 Sut05 TBN06 Sutter, H., The free lunch is over: A fundamental turn toward concurrency in software. Dr. Dobbś Journal, 30,3(2005), sivut 202 210. Tulip, J., Bekkema, J. ja Nesbitt, K., Multi-threaded game engine design. IE 06: Procedings of the 3rd Australasian conference on Interactive entertainment, Murdoch University, Australia, Australia, 2006, Murdoch University, sivut 9 14.