Koodaripiireissä on varsin yleistä suhtautua suorituskykyyn hyvin pakkomielteisesti. Koodin suoritusajasta viilataan pois viimeisimpiäkin löysiä millisekunteja ja muistikopiointia koitetaan välttää viimeiseen asti kuin ruttoa. Lopputuloksen kannalta tällä saattaa olla vaikutusta - tai sitten ei. Suorituskyvyllä on merkitystä, mutta enimmäkseen vain oikeassa paikassa.

Verkkosivustojen tapauksessa sivuston palvelinpään koodin (eli verkkosovelluksen palvelinosan) suorituskyvyllä ei juurikaan ole merkitystä. Yleensä sovelluksen edessä istuskelee jonkinlainen välimuistikerros, jonka tehtävänä on tarjoilla vastaukset ylivoimaiseen valtaosaan sivustolle tehtävistä pyynnöistä. Sovelluksen taustalla taas on yleensä tietokanta, joka on pääsääntöisesti pullonkaulana vähänkään vaativammissa operaatioissa. Mikäli sovelluksen palvelinpään toteutuksessa ei ole tehty jotakin täysin aivokuollutta, on sen osuus sivupyynnön vasteajasta varsin vaatimaton.

Suorituskyky merkitsee aina eniten siellä, missä on vähiten resursseja käytettävissä. Verkkosivustojen tapauksessa se on nykyään asiakaspäässä, sillä kasvavan mobiilikäytön myötä on päätelaitteena yhä useammin rajallisilla resursseilla varustettu älypuhelin. Vaikka älypuhelimet ovatkin modernin teknologian riemuvoitto ja sinällään aivan huikea saavutus, joudutaan niissä ymmärrettävästikin tekemään kompromisseja koon ja akkukeston vuoksi. Huonolla asiakaspään suorituskyvyllä toimivat verkkosivustot aiheuttavat käyttäjille huonoja käyttökokemuksia ja imevät mobiilikäyttäjän laitteen akun kuiviin hämmästyttävän nopeasti. Tässä kirjoituksessa käsitellään muutamia käyttäjäkokemukseen vaikuttavia seikkoja erityisesti mobiilikäytön näkökulmasta.

Puoli kilo(tavu)a päivässä

Asiakaspään suorituskyvyn parantamiseen liittyy erittäin oleellisena osana tiedonsiirron optimointi. Mobiilikäytössä yhtenä hankalana muuttujana ovat vaihtelevat verkko-olosuhteet. Yhden sivupyynnön aikana päätelaite saattaa vaihtaa verkkoa useampaankin otteeseen ja tiedonsiirrossa käytetyt paketit voivat kulkea palvelimen ja päätelaitteen välillä milloin mitäkin reittiä. Tämä tekee tiedonsiirrosta epävarmaa ja hyvin hidasta. Koska toisinaan onkin suoranainen ihme, että tietoa liikkuu ylipäänsä ollenkaan päätelaitteen ja palvelimen välillä, olisi loistavaa, jos tiedonsiirtoa tehtäisiin mahdollisimman vähän.

Hyvä paikka aloittaa siirrettävien pakettien eliminoiminen on hyödyntää selaimen välimuistia. Usein melkein kaikki staattiset resurssit (kuten kuvat tai tyyli- ja JavaScript-tiedostot) voidaankin huoletta jättää selaimen välimuistiin, jolloin niitä ei tarvitse käydä lataamassa palvelimelta kuin kerran. Tämä vähentää palvelimelle tehtäviä pyyntöjä (Wuhuu! Palvelinkin tykkää!) ja resurssit toimivat, vaikka verkko katoaisi kokonaan.

Niin palvelin vastaa kuin siltä pyydetään

Jotta pyynnöt osataan tehdä oikealle palvelimelle, tulee palvelimen IP-osoite selvittää ensin nimipalvelimelta. Nämä ns. DNS-lookupit ovat edellytyksenä minkäänlaisen tiedonsiirron aloittamiseksi, mutta ne tehdään kuitenkin vain kertaalleen jokaista viitattua domainia kohti. Niiden määrän minimoiminen on kuitenkin järkevää, joten sisältöä kannattaa siksi pyrkiä tarjoilemaan mahdollisimman paljon samasta domainista. Toisaalta esimerkiksi joidenkin sisältökuvien tarjoileminen eri domainista on järkevää, koska selaimet rajoittavat kullekin palvelimelle yhtäaikaa auki olevien pyyntöjen määrää. Jakamalla pyynnöt siis kahteen domainiin saadaan tuplattua käynnissä olevien pyyntöjen määrä.

Koska jokaisesta HTTP-pyynnöstä tulee ylimääräistä verkkoliikennettä varsinaisen siirrettävän datan lisäksi, pitäisi pyyntöjen kokonaismäärä pyrkiä tiputtamaan mahdollisimman alhaiseksi. Onkin pääsääntöisesti järkevämpää pyrkiä tarjoilemaan yksi isompi tiedosto monen pienemmän asemesta. Tähän päästään yhdistämällä resursseja toisiinsa, joskin monissa tapauksissa sivuston rakenne sanelee, mitkä resurssit on järkevää yhdistää toisiin ja mitkä kannattaa tarjoilla itsekseen.

Kaikki siirrettävät tiedostot kannattaa myös pakata. Tekstimuotoisten tiedostojen (HTML-dokumenttien, CSS-tyylien ja JavaScript-tiedostojen) pakkaamiseen on suositeltavaa käyttää gzip-pakkausta. Gzip on erittäin helppo ottaa käyttöön, sillä HTTP-palvelinohjelmistoilla (kuten Nginx ja Apache) on todella hyvä tuki pakkauksen tekemiseen "lennossa" ja selaimet osaavat myös automaattisesti purkaa ko. pakkausalgoritmilla pakatut tiedostot.

Kaiken tämän lisäksi varsin oleellista lopullisen käyttökokemuksen kannalta on se, missä vaiheessa tietoa siirretään. Optimaalista olisi, jos sivun tärkeimmät osat voitaisiin esittää kävijälle mahdollisimman aikaisin ja vasta sitten sivulle tuotaisiin vähemmän merkittävät asiat. No, tämähän on usein helpommin sanottu kuin tehty.

Montaa asiaa kerralla

Jotta sivun tärkein sisältö voitaisiin näyttää kävijälle mahdollisimman nopeasti, pitäisi sivusta olla ladattuna kyseinen sisältö sekä riittävä määrä tyylejä. Suurin ongelma tulee siinä, kun pitää päättää, mikä määrä tyylejä oikeasti riittää. Jos sivustolla esimerkiksi käytetään jotakin erikoiskirjasinta, voi tuntua järkevältä näyttää sisältö ensin jollakin fiksulla peruskirjasimella ja antaa selaimen vaihtaa kirjasin sitten oikeaksi, kun se on joskus latautunut. Tällaisessa tilanteessa tulee ongelmia.

Käytännössä verkkosivustot koostuvat enimmäkseen laatikkomaisista elementeistä, jotka asetellaan selainikkunaan tyylimääritysten perusteella. Pääasiallisesti selaimet on kuitenkin rakennettu ensisijaisesti tekstuaalisen sisällön esittämiseen, joten niiden toimintatapa perustuu valtaosin sisällön asetteluun normaalin tekstijuoksutuksen (tai dokumenttivuon, jos haluaa käyttää suoraa käännöstä termistä "document flow") mukaan. Kun sivun kirjasin muuttuu, muuttuu usein koko sivun asettelu ja selaimen pitää laskea uudelleen kaikkien elementtien koot ja paikat. Tämä aiheuttaa ryppyjä käyttökokemukseen ja käyttäjän otsaan, kun sisältö näyttää kesken lukuprosessin hypähtävän aivan eri paikkaan ja mahdollisesti vieläpä aivan erinäköisenä.

Sama ongelma pätee myös lähes kaikkiin muihin tyyleihin, sillä ne käytännössä aina muokkaavat jotakin osaa sivun asettelusta. Kun jonkin elementin asettelu muuttuu, se usein vaikuttaa koko dokumenttiin ja selain joutuu näin useaan otteeseen laskemaan elementeille uusia paikkoja ja piirtämään sivun elementtejä uusiksi. Näiden ns. reflow- ja repaint-tapahtumien välttäminen on käyttökokemuksen ja sivun yleisen suorituskyvyn kannalta elintärkeää. Jokainen tällainen tapahtuma vie paitsi käyttäjän hermot myös osan mobiililaitteen akusta.

Miten resursseja sitten voidaan ladata asynkronisesti ilman, että käyttökokemus muuttuu sietämättömäksi? Tähän ei ole mitään yhtä yleispätevää vastausta, mutta hatara yleislinja voisi olla jokseenkin seuraava:

1. Ladataan mahdollisimman nopeasti ja kevyesti kaikki sivun tärkeimmän sisällön esittämiseen vaaditut tyylit. Tämä tarkoittaa, että kaikki sellaiset tyylit, jotka aiheuttaisivat mittavia reflow- ja repaint-tapahtumia, pitää saada mukaan ensimmäisten sivulle ladattavien asioiden joukossa.

2. Siirretään asynkronisesti ladattavia tyylejä käyttävät elementit kokonaan pois normaalista dokumenttivuosta, jotta niiden koko- tai asettelumuutokset eivät vaikuta muun dokumentin asetteluun. Tämä onnistuu käyttämällä CSS-tyylejä, jotka pitäisi olla ladattuna edellisessä kohdassa esiteltyjen asioiden kanssa.

3. Lisätään asynkroniset tyyli- ja JavaScript-tiedostot dynaamisesti perustauhkojen lataamisen jälkeen sivulle. Tässä ei pitäisi muuttaa dokumentin rakennetta siten, että selain joutuu uudestaan laskemaan sivun asettelun.

4. Ladataan resursseja valmiiksi "luppoajalla" selaimen välimuistiin. Tässä ideana on ladata resursseja valmiiksi selaimen välimuistiin sillä hetkellä, kun kävijä lukee valmista, ladattua sivua. Lähtökohtana tässä touhussa on tietysti se, että kävijä viettää sivustolla aikaansa enemmän kuin vain yhden pienen piipahduksen verran. Satunnaiskäväisyssä vaarana on, että kaikki siirretty data ja käytetty laskentateho on kulutettu täysin turhaan, mikä on paitsi tyhmää, myös epäkohteliasta.

Toimistotyöntekijä kirjoittaa läppärillä

Erikoisfonteista riesaa

Kuten jo aikaisemmin mainitsinkin, erikoiskirjasimet saattavat muodostua ongelmalliseksi tapaukseksi sivustojen rakentamisessa mahdollisimman mobiiliystävälliseksi. Moderneilla verkkosivustoilla halutaan panostaa hyvään typografiaan, mikä yleensä saakin sivustot näyttämään huolitelluilta ja tukee hyvin yritysten markkinointiviestintää. Kirjasinten lisensointi ja käyttöehdot ovat kuitenkin synnyttäneet ihan oman liiketoiminta-alansa: pilvikirjasinpalvelut. Nämä palvelut mahdollistavat kaupallisilla lisensseillä olevien kirjasinten käyttämisen verkkosivustoilla niin, että käyttöehtoja ei rikota ja kirjasinten käytöstä maksetaan lisenssimaksu niin kuin pitää. Kaikilla tällaisilla palveluntarjoajilla on jokin oma sisällönjakeluverkostonsa, jonka ansiosta kirjasinten lataamisen pitäisi olla supernopeaa ja vaivatonta kaikkialla maailmassa. Ihan kaikkia ongelmia tälläkään ei pystytä korjaamaan.

Vaikka tiedonsiirto selaimen ja kirjasintiedoston lähdepalvelimen välillä toimisikin nopeasti, tulee kirjasimen vaihdosta melkein väkisin vähintään muutama reflow- ja repaint-tapahtuma lisää. Tämä on heikompitehoisilla mobiililaitteilla yllättävän raskasta, sillä modernitkaan mobiiliselaimet eivät vielä tällä hetkellä pysty kovin hyvin hyödyntämään laitteiden sinällään varsin merkittävää rinnakkaislaskentatehoa. Tähän voidaan koittaa jonkin verran vaikuttaa yllä olevan listan kohdassa kaksi mainituilla toimenpiteillä, mutta niiden vaikutus on useimmiten varsin pieni. Lisäksi myös tekstin piirtäminen on jo itsessään ihan vaativaa työtä, jota ei oikein voi siirtää esimerkiksi grafiikkaprosessorin hommaksi. Tästä hyvänä esimerkkinä toimii Mozillan Servo-selainprojekti, joka pyrkii hyödyntämään grafiikkaprosessoria niin paljon kuin mahdollista, mutta sekin joutuu kirjasinpiirrossa tukeutumaan pelkästään keskusprosessoriin.

Äsken mainittu Servo-selainprojekti antaa joka tapauksessa viitteitä siitä, että suuri osa sivun asettelun laskennasta on rinnakkaistettavissa ja että siitä myös on hyötyä. Servolla onkin saavutettu järjettömän kovia suorituskykylukemia, vaikka sitä ei ihan oikeaan, käyttökelpoiseen selaimeen voi vielä (tai koskaan) verratakaan. Tällä hetkellä näyttää siltä, että paras tapa pyrkiä tekemään verkkosivuista lähitulevaisuuden selaimille mahdollisimman ystävällisiä, on rakentaa sivut tukemaan helppoa rinnakkaislaskentaa. Tämä tarkoittaa, että selaimelle pyritään antamaan viitteitä siitä, mitkä osat sivuista voidaan käsitellä omina kokonaisuuksinaan, jolloin niiden prosessointi voidaan helposti siirtää rinnakkaiseksi muiden vastaavien kanssa.

Maailma pysähtyy roskakuskille

JavaScript tukeutuu muistinhallinnassa roskankeruusykleihin, joiden tehtävänä on tarvittaessa etsiä muistista tarpeettomat objektit ja vapauttaa niiden varaama muisti takaisin käyttöön. Lähes kaikki modernit selaimet ajavat JavaScriptiä mallilla, joka toteuttaa ns. generationaalisen roskankeruualgoritmin. Tässä mallissa muistissa olevat objektit jakautuvat kahteen erikokoiseen sukupolveen: suurempaan vanhaan ja pienempään nuoreen sukupolveen. Jos objektit selviävät kahdesta roskankeruusyklistä pienen muistitilan nuoressa sukupolvessa, ne siirretään vanhan sukupolven asuttamaan, suurempaan muistitilaan.

Ylivoimainen valtaosa JavaScript-koodin luomista objekteista on väliaikaisia, joten niiden varaama muisti voidaan vapauttaa hyvin pian niiden luomisen jälkeen. Siksi onkin järkevää, että nuoren sukupolven roskankeruu tehdään varsin usein käyttäen mahdollisimman nopeaa ja kevyttä roskankeruualgoritmia. Googlen V8-JavaScript-moottori käyttää tähän scavenge-roskankeruualgoritmia, joka sopii erittäin hyvin käytettäväksi pienemmillä muistialueilla. Yleensä yksi roskankeruusykli kestää alle 30 millisekuntia.

Vanhan sukupolven roskankeruussa puolestaan on tarpeen käyttää algoritmia, joka on huomattavasti hitaampi, mutta joka sopii paljon laajempien muistialueiden roskankeruuseen. Tässä yleisesti ovat käytössä mark-and-sweep- sekä mark-compact-algoritmit, jotka käynnistyvät, kun tietty määrä objektien osoittamaa muistia on siirtynyt nuoresta vanhaan sukupolveen. Tämä vanhan sukupolven roskankeruu on prosessina todella paljon nuoren polven roskankeruuta hitaampi ja saattaa kestää joissakin tapauksissa moderneissa selaimissa jopa useita satoja millisekunteja.

No, eiväthän nuo kuulosta niin kovin pitkäkestoisilta hommilta, joten äkkiseltään katsottuna tällaisiin asioihin ei kannattaisi juurikaan paneutua. Roskankeruussa kuitenkin on yksi asia, joka tekee siitä suorituskyvyn kannalta erittäin merkittävän: maailma pysähtyy roskankeruuprosessin ajaksi. JavaScript-koodin suorittaminen joudutaan pysäyttämään kokonaan kummankin roskankeruuprosessin ajaksi, koska roskankeruun kannalta tärkeä objektigraafi ei saa muuttua roskankeruun aikana. Tämä tarkoittaa, että esimerkiksi JavaScript-animaatiot pysähtyvät siksi aikaa, kun roskankeruu on käynnissä ja jatkavat sitten rullaamistaan, kun prosessi on ohi. Animaatioissa lyhyetkin tauot näkyvät erittäin selvästi, sillä yhden animaatioruudun tulisi pysyä näkyvissä vain noin 16,6 millisekuntia, jotta saavutettaisiin sulava 60 ruudun päivitystahti sekunnissa.

Tökkivät animaatiot ovat toki kiusallisia, mutta ne eivät sentään totaalisesti tuhoa käyttökokemusta. Roskankeruu pystyy kyllä siihenkin. Jos roskankeruusyklit seuraavat nopealla tahdilla toisiaan, muuttuu selailukokemus todella tahmeaksi ja pahimmassa tapauksessa selain menee täysin käyttökelvottomaksi. Selaimet onneksi nykyään itse tajuavat tämän ja esittävät kävijälle mahdollisuuden lopettaa hankalasti käyttäytyvän skriptin suorittaminen. Tällaisessa tilanteessa usein käyttäjällä on kuitenkin jo mennyt hermo, eikä sivu luultavasti kuitenkaan toimi kovin hyvin ilman kyseisen skriptin suorittamista. Käyttökokemuksena tämä on vähintäänkin kehno.

Mitä verkkosivun suorituskyvylle voi tehdä?

Verkkosivun suorituskyvyn optimointi on hyvä aloittaa miettimällä vastauksia seuraaviin kysymyksiin:

1. Mikä on sivun keskeisin sisältö?

2. Mikä osa sivusta voidaan pitää asiakaspäässä välimuistissa?

3. Mikä juuri tällä sivulla on toissijaista tai jopa täysin turhaa?

4. Mikä on riittävä minimitaso, jolla sivun sisällön voi esittää kävijälle?

5. Mitä sivun sisällöstä voidaan ladata vasta "sitten joskus"?

6. Mitkä osat sivusta voidaan prosessoida rinnakkain toistensa kanssa?

7. Voidaanko jotakin muita sivuston tai sovelluksen osia ladata etukäteen välimuistiin?

Verkkosivun suorituskyvyn optimointi on mobiiliselailun kasvaessa entistä suuremmassa osassa kävijöiden käyttökokemuksen kannalta. Modernit selaimet kuitenkin tarjoavat mainiot mahdollisuudet tehdä erittäin näyttävistä ja houkuttelevista sivustoista toimivat ja nopeat niin työpöydällä kuin älypuhelimessakin.