Category Archives: Arkkitehtuuri

Kaikki ohjelmistoarkkitehtuuriin liittyvä

Porttautuva koodi mobiilissa

“Write once, run anywhere” oli Javan silver bullet -mantra aikanaan. Eli kirjoitat koodin kerran, käännät sen kerran ja ajat missä vaan tuetussa ympäristössä ilman että ohjelmasi tarvitsee liiemmin tietää ajonaikaisesti mikä käyttöjärjestelmä / rauta ympäristössä on käytössä. No, vaikkei tuohon ole vieläkään – 18 vuotta myöhemmin – ole ihan päästy, on Javan ja vaikkapa Pythonin kaltaisten tulkattujen/scriptauskielten kanssa useimmiten paljon helpompaa elää monialustaympäristössä kuin natiivikoodin. Mutta entä jos halutaankin kehittää natiivisovellus jossa uudelleenkäytetään mahdollisimman suuri osa koodista sen sijaan että N alustaa varten tehdään N toteutusta? Vaikka nykyisten mobiililaitteiden rautaspeksejä (kotitehtävä: montako 25MHz i386 CPU:ta tarvitaan tuottamaan Samsung Galaxy S4:n CPU-teho?) ihmetellessä ei parhaalla omallatunnollakaan voi sanoa resurssien puutteen pakottavan natiivikoodiin, on sen käyttö esim. Android-alustallakin perusteltua sovelluksissa joiden on puristettava se viimeinenkin pisara suorituskykyä irti laitteesta.

Luonnollisesti kaikenlaisiin sovellustyyppeihin porttautuvaa koodia on turha yrittää ujuttaa; jos sovellus on 80%:sti käyttöliittymää eikä ole tarjolla/ei haluta käyttää kaikilla kohdealustoilla toimivaa porttautuvaa UI-kirjastoa, ei kannata väkisin lyödä päätään seinään vaan kirjoittaa toteutukset kiltisti alustan natiivi-API:a käyttäen – tai valita teknologiaksi HTML5 joka sopii webmäisiin lomakesovelluksiin kuin nyrkki silmään. Ym. UI-kirjastoa käyttämällä saatetaan myös – sen toteutuksesta toki riippuen – menettää alustan oma look-and-feel. Ihan hihasta ravistettuna porttautuvan arkkitehtuurin puolesta / vastaan listat voisivat näyttää vaikkapa tältä:

Vastaan:

  • Väkisin yritetty porttautuvuus tekee arkkitehtuurista kömpelön ja lisää turhaa kompleksisuutta
  • Alustan natiiveja ominaisuuksia saatetaan menettää (hyvässä ja pahassa)
  • Käytettäväksi valittu yhteinen ohjelmointikieli ei välttämättä ole tuttu kaikille projektin jäsenille

Puolesta:

  • Koodin joutuu kirjoittamaan vain kerran eli kehityskustannus pienenee
  • Bugit joutuu korjaamaan vain kerran
  • Koodipohjasta tulee merkittävästi pienempi jolloin sen ylläpidettävyys paranee ja siihen on nopeampaa perehdyttää uusia tekijöitä
  • Ohjelmiston luonteen salliessa paljon koodia kierrättävä arkkitehtuuri on hyvin elegantti

Esimerkkiprojekti: MMark13 – mobiilia suorituskykytestausta

Käytän esimerkkinä kuluneen vuoden aikana hiljalleen syntynyttä harrasteprojektiani MMark13, joka – hyvin tiivistetysti – on mobiililaitteiden CPU/GPU suorituskyvyn mittaamiseen tarkoitettu työkalu. Vastaavalla idealla (joskin merkittävästi isommalla tiimillä & budjetilla :)) rakennettu sovellus löytyy Desktop-puolelta, monelle tuttu suomalaista tekoa oleva Futuremark:in 3DMark. Ja ei, en ole suoraan rinnastamassa harrastelijatuotostani PC-maailman (Ja Futuremark:han puuhaa parasta aikaa myös mobiilibenchmarkingia, Androidille se jo ilmestyikin! -toim.) uraauurtavaan tuotteeseen.. no joka tapauksessa, projekti lähti liikkeelle halusta hioa/päivittää OpenGL ES 2.0 taitoja ja havaittuani ettei tuossa vaiheessa (alkuvuosi 2012) juuri vastaavia sovelluksia ollut tarjolla (paitsi kaupallinen ja maksullinen GLBenchmark 2.5 – joskin tuolloin vain iOS:lle), valitsin kyseisen sovellusalueen jo siksikin että porttautuvan arkkitehtuurin hyväksikäyttäminen laajalti tarjosi ainutlaatuisen edun: kun jokaisella alustalla ajettava koodi on 100% sama niiltä osin jotka grafiikan piirtoon / mittauksiin vaikuttavat, voidaan tuloksia verrata 1:1 toisiinsa alustojen välillä ilman että tarvitsee napista Javan tehottomuudesta Androidilla.

 

MMark13 koodin rakenne

 

Koodin uudelleenkäytön suunnittelussa on hyvä lähteä liikkeelle siitä että kartoittaa tarkasti kohdealustansa ja niiden tarjoamat asiat ja sitten hahmottelee niiden pohjalta suurimman yhteisen nimittäjän. Valitsin kohdealustoikseni iOS (Applen laitteet) ja Qt (Android, kaikki merkittävät desktop-käyttöjärjestelmät, Meego, Blackberry10, Symbian, Sailfish OS, Ubuntu Phone, luultavasti myös Tizen ja Firefox OS). Kun alustat ovat tiedossa, aloittaa voi vaikka ohjelmointikielestä; se on valinta jota alemmalle tasolle ei juuri pääse. C olisi aina varma valinta, mutta puhtaalla C:llä syntyy tulosta kovin hitaasti ja dynaamisten tietorakenteiden / merkkijonojen / jne käsittely on melko kömpelöä. Valitsinkin siksi pykälää korkeamman abstraktiotason eli C++:n. Ohjelmointikielen version valintaan kannattaa paneutua myös hetken eikä sännätä käyttämään heti uusinta; itse en uskaltanut vielä tähän projektiin valita tuoreinta C++11 versiota vaikka se oliskin tarjonnut muutamat elämää helpottavat asiat kuten “auto” tyypin muuttujille, sisäänrakennetun säiekirjaston ja paljon muuta. Sittemmin olen perehtynyt tarkemmin C++11 feature support matriisiin merkittävillä kääntäjillä (GCC, clang, MSVC++) ja vaikuttaa että aika on kypsä sen käyttöönotolle.

Ohjelmointikielen valinnan jälkeen seuraava looginen askel on kartoittaa APIen/ulkoisten kirjastojen tarve ja koettaa vaikkapa proof-of-concept -hengessä varmistaa että kirjastovalinnat täyttävät tarpeet. MMark13 -projektissa tarvitsin säikeistystä, joten käytin hyväkseni tietoa että kaikki kohdealustani ovat UNIX-perillisiä ja siten tarjoavat POSIX threads (pthreads) paketit. Tarvittiin myös JSON kirjastoa; pienellä googlauksella löysin “jsoncpp” paketin jonka koodi/dokumentaatio oli poikkeuksellisen laadukkaan oloista ja joka istui suoraan tarpeisiini. Kenties tuosta löydöksestä riemuitessani kirjoitin seuraavan askeleen eli tuloslaskelma-JSON lähetyksen palvelimelle abstraktion läpi siten että kirjoitin alustoille omat toteutuksensa sen sijaan että olisin käyttänyt ilmiselvää ratkaisua libcurlia. Tajusin mokani vasta projektin loppumetreillä enkä rupea yleensä korjaamaan jotain joka toimii, joten saivat jäädä. Lisäksi otin mukaan simppelin C++ MD5-toteutuksen. Halusin toteuttaa softani käyttöliittymän alustariippumattomasti siten että se käyttäytyy kaikilla laitteilla samalla tavalla ja myös näyttää samalta. Etsinkin melko laajalti OpenGL-pohjaista Widget-kirjastoa mutta löytämäni paketit olivat joko aivan järjettömiä bloatteja tai sitten erittäin huonolaatuisen näköistä koodia (usein molempia) että päädyin kirjoittamaan hyvin lightweight toteutuksen aivan alusta erään intensiivisen viikonlopun aikana. Eli ainakin yksi pyörä tuli keksittyä uusiksi – toisaalta aikaa ei tuohon uponnut paljoa, lopputulos miellyttää silmää, homma oli erittäin hauskaa ja lisäksi vieläpä opin paljon.

Lisäksi tarvittiin seuraavat asiat alustariippuvasti: softan bootstrapping, softa/hardisinformaation penkomista laitteelta, joitain sekalaisia pikkujuttuja sekä resurssifilejen lukeminen systeemistä – tähän olisi voinut käyttää ihan fread():ia mutta halusin toisaalta käyttää Qt:n mainiota resource systeemiä jonka kanssa tuo taas ei toimi. Alla muutama konkreettinen koodiesimerkki alustariippuvasta koodin jaosta:

CommonFunctionsQT.cpp:

std::string RandomUuid()
{
    // .mid(1,36) strips the curly braces added by toString(), duh
    return std::string(QUuid::createUuid().toString().mid(1,36).toUtf8());
}

CommonFunctionsIOS.cpp:

std::string RandomUuid()
{
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef cfuuid = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    NSString* uuid = (NSString*)cfuuid;
    [uuid autorelease];

    return std::string([uuid UTF8String]);
}

Kurkistetaan vielä korkealla tasolla ohjelmiston toimintaa ja sitä miten alustariippuva ja porttautuva osa koodia pelaavat yhteen. iOS-buildissa tarvittava wrapperkoodi on luonnollisesti Objective-C(++):aa; kokemus osoitti ettei sen ja C++ -maailman sekoittaminen keskenään ole triviaalia tai kovin eleganttia, ja kehotankin eristämään kyseisen rajapinnan mahdollisimman pienelle alalle. Käytännössä toimiva pattern on antaa Objective-C++ koodille yksisuuntainen has-a -patternin mukainen suhde porttautuvaan C++ koodiin ja rakentaa iOS-specific toteutus C++ luokalle joka hoitaa callbackien tarvitseman alustariippuvan koodin. Qt maailma on natiivisti C++:aa, joten siellä ei tarvittu taikatemppuja. Ohjelman toimintalogiikka pääpiirteittäin:

  1. Alustariippuvainen osa ottaa vastaan inputit, muuttaa niiden datatyypit alustariippumattomaan muotoon ja välittää ne eteenpäin allaolevalle porttautuvalle osalle. Käytännössä inputit ovat:
    • Redraw timer. iOS:ssa CADisplayLink, Qt:ssä QTimer.
    • Käyttäjän kosketukset. iOS:ssa UITouch*, Qt:ssa QTouchEvent.
  2. Alustariippumaton osa päivittää tilan / menussa ollessa UI:n tilan touchien / kuluneen ajan avulla
  3. Uuden tilan perusteella piirretään uusi frame sekä mm. aloitetaan fysiikkalaskenta seuraavaa framea varten
  4. Tarvittaessa kutsutaan abstraktoituja callbackeja kun tarvitaan alustariippuvaa toiminnallisuutta; esimerkiksi kun ladataan testin vaatimia tekstuureita levyltä tai halutaan lähettää tulokset serverille

Jollekulle on saattanut tätä lukiessa tulla mieleen että mitenkäs se Windows Phone sitten? WP8 pois jättämiseen kohdealustojen joukosta on oikeastaan vain yksi syy – OpenGL APIn puute. Vaikka ymmärtääkseni WP8 hardis on OpenGL ES 2.0 yhteensopivaa, Microsoft ei tapansa mukaan tarjoa standardia rajapintaa vaan pakottaa kehittäjät porttaamaan koodinsa Direct3D:lle. Ja koska softani tärkeimpiä ominaisuuksia on olla suoraan vertailukelpoinen ympäristöjen välillä, ei tuo oikein ollut optio. Jos Googlen ANGLE projektista tulee ikina luotettava port WP:lle *tai* jostain taivaasta putoaa OpenGL ajuri, teen ilomielin myös WP-buildin. Tämän vuoden aikana on tarkoitus suoltaa ulos buildit ainakin Blackberry10:lle, Sailfish OS:lle sekä Ubuntu Phonelle riippuen siitä saadaanko moisia laitteita käsiimme.

Hyvää kevättä kaikille!

Linkkejä

Lisää luettavaa: