Anders Jonsson
anders.jonsson@norsjovallen.se 2015

# Vad optimerar vi?

När vi optimerar för GNOME är den första saken att komma ihåg detta: vi försöker inte göra programmet bättre, vi försöker göra personen som använder datorn gladare.

Bättre program gör folk gladare, men det finns några förbättringar som kommer göra dem mycket gladare än andra: svarstid, uppstartstid, lättåtkomliga kommandon och att datorn inte övergår till växlingsminne så fort fler än två program är öppna.

Traditionell optimering behandlar koncept som CPU-användning, kodstorlek, antalet musklick och minnesanvändningen för programmet. Denna andra lista har valts för att korrelera med den första listan, men det finns en viktig skillnad: Personen som använder GNOME bryr sig inte om den andra listan, medan de bryr sig mycket om den första listan. Då vi optimerar GNOME-program kommer vi minska CPU-användning, minnesanvändning och alla de sakerna, men dessa är medel för att nå målet, de är inte målet i sig. Vi optimerar för människor.

# Göra optimeringen

Det föregående avsnittet förbisåg en viktig egenskap: för att optimera något måste det vara mätbart. Du kan inte mäta glädje. Om du mäter uppstartstid kan du dock se om du har förbättrat den. Förhoppningsvis kommer även glädje att följa på detta.

Optimering är en process bestående av mätning, förfining och ny mätning. Det första du måste göra är alltså att hitta ett sätt att mäta det som du optimerar. I idealfallet är detta mätvärde en enda siffra, till exempel tiden för att utföra en viss uppgift. Detta är ditt prestandatest, det är det enda sättet att avgöra om du vinner eller förlorar. Det är stor skillnad mellan ett program som borde vara snabbt och ett program som är snabbt.

Då du väl har ett grundläggande prestandatest behöver du komma på varför din kod inte fungerar så bra som den borde. Det är frestande att göra detta via inspektion: att bara titta på koden och försöka hitta något som ser ut som att det behöver förbättras. Du kommer helt säkert ha fel. Att använda en profilerare för att få en detaljerad sammanställning över vad ditt program verkligen gör är enda sättet att vara säker.

Vanligen är problemet isolerat till små stycken kod. Välj det värsta stället och koncentrera dig på det först. Då det väl är klart kan du köra om profileraren och upprepa proceduren. Då du fortsätter kommer vinsterna du gör i varje steg bli mindre och mindre, och vid någon punkt måste du avgöra om resultaten är bra nog. Om dina ansträngningar endast orsakar förbättringar på 10% är du sedan länge förbi punkten då du borde ha slutat.

Glöm inte helhetsbilden. Till exempel kan du istället för att försöka göra ett stycke kod snabbare fråga dig själv om det över huvud taget behöver köras. Kan det kombineras med annan kod? Kan resultatet från tidigare beräkningar sparas och återanvändas? Det behöver inte ens optimeras om det är på ett ställe så att användaren aldrig kommer att upptäcka det. Optimeringsförsök är än värre om koden redan är optimerad och gör de tunga beräkningarna nu för att undvika att göra dem senare. Kod körs inte isolerad, och detsamma gäller även optimeringsprocessen.

# Tips

## Grunderna

Kör om ditt prestandatest efter varje ändring du gör i koden och behåll en logg över allt du ändrar och hur det påverkar prestandatestet. Detta låter dig göra misstag ogjorda och hjälper dig även att inte upprepa misstag.

Försäkra dig om att din kod är korrekt och felfri innan du optimerar den. Kontrollera att den förblir korrekt och felfri efter optimering.

Optimera på hög nivå innan du optimerar detaljerna.

Använd rätt algoritm. Det klassiska skolboksexemplet är att använda kvicksortering istället för bubbelsortering. Det finns många andra, vissa sparar minne, andra sparar CPU. Se även vad du kan ta för genvägar: du kan sortera snabbare än kvicksortering om du är redo att göra kompromisser.

Optimering är en avvägning. Att cacha resultat snabbar upp beräkningar, men ökar minnesanvändning. Att spara data till disk sparar minne, men kostar tid då det läses tillbaka från disken.

Säkerställ att du väljer en bred mångfald av indata att optimera mot. Om du inte gör detta är det lätt hänt att du i slutändan har kod som är noggrant optimerad mot en fil och inga andra.

Undvik dyra operationer: många små diskläsningar. Att använda upp mycket minne så att användning av växlingsminne blir nödvändig. Undvik allt som skriver till eller läser från hårddisken i onödan. Nätverket är också långsamt. Undvik även grafikåtgärder som behöver ett svar från X-servern.

## Fällor för den oförsiktige

Var uppmärksam på sidoeffekter. Det kan ofta finnas märkliga interaktioner mellan olika stycken kod, en hastighetsökning i en del kan sakta ner en annan del.

När du tar tid på kod så bidrar händelser utanför programmet till störningar till tidtagningsresultaten, detta även på ett system i vila. Ta ett genomsnitt över flera körningar. Om koden är väldigt kort är även upplösningen på tidsberäkningen ett problem. I detta fall får du mäta tiden datorn tar för att köra koden 100 eller 1000 gånger. Om tiderna du mäter är längre än några få sekunder är det troligen OK.

Det är lätt att bli vilseledd av profileraren. Det finns historier om folk som optimerat operativsystemets viloslinga för att det är där det tillbringar all sin tid! Optimera inte kod som inte gör något användaren är intresserad av.

Kom ihåg resurserna på X-servern. Ditt programs minnesanvändning inkluderar inte pixmappar som lagras i X-serverns process, men de använder fortfarande minne. Använd xrestop för att se vilka resurser ditt program använder.

## Tips på låg nivå

När du optimerar minnesanvändning får du vara uppmärksam på skillnaden mellan maximal användning och genomsnittlig minnesanvändning. Visst minne är nästan alltid allokerat, detta är oftast dåligt. Annat är bara allokerat kort tid, detta kan vara acceptabelt. Verktyg som massif använder istället konceptet utrymmestid, produkten av använt minne och tidslängden det var allokerat.

Ta tid på förenklade kodstycken som endast gör de väsentliga sakerna, detta ger dig en absolut lägre gräns på tiden din kod kommer ta. Om du till exempel optimerar en slinga kan du ta tid på en tom slinga. Om detta fortfarande tar för lång tid kan ingen mikrooptimering hjälpa dig, och du måste ändra din design. Säkerställ att kompilatorn inte optimerar bort din tomma slinga.

Flytta ut kod som är i slingor. En något mer komplicerad kod som körs en gång är mycket snabbare än ett enkelt kodstycke som körs tusen gånger. Undvik att anropa långsam kod ofta.

Ge kompilatorn så många tips som möjligt. Använd nyckelordet const. Använd `G_INLINE_FUNC` för korta, ofta anropade, funktioner. Ta en titt på `G_GNUC_PURE`, `G_LIKELY` och de andra diversemakrona hos glib. Använd makrona istället för gcc-specifika nyckelord för att säkerställa portabilitet.

Använd inte assemblerspråk. Det är inte porterbart, och även om det kan vara snabbt på en processor så är det inte garanterat att vara snabbt på alla processorer som stöder den arkitekturen (t.ex. Athlon kontra Pentium 4).

Skriv inte om en existerande biblioteksrutin om du inte är säker på att den är onödigt långsam. Många CPU-intensiva biblioteksrutiner har redan optimerats. Omvänt är vissa biblioteksrutiner långsamma, särskilt sådana som gör systemanrop till operativsystemet.

Minimera antalet bibliotek som du länkar till. Ju färre program att länka in, desto snabbare startar programmet. Detta är svårt att göra med GNOME.

## Trick på hög nivå

Dra nytta av samtidighet. Detta betyder inte bara att använda flera processorer, det betyder också att dra nytta av tiden användaren använder för att tänka på vad de ska göra härnäst för att utföra några beräkningar i förväg. Gör beräkningar under väntan på att data ska läsas från disk. Dra nytta av flera resurser, använd dem alla samtidigt.

Fuska. Användaren behöver bara tro att datorn är snabb, det spelar ingen roll om den faktiskt är det eller inte. Det är tiden mellan kommandot och svaret som är viktig, det spelar ingen roll om svaret är beräknat i förväg, cachat, eller faktiskt kommer utarbetas senare vid ett lämpligare tillfälle, så länge användaren får vad de förväntar sig.

Gör saker i viloslingan. Det är enklare att programmera än fullständig flertrådning men får ändå saker gjorda utan att användaren märker det. Var dock försiktig, om du tillbringar för mycket tid i viloslingan kommer ditt program att kännas trögt. Ge därför med jämna mellanrum kontroll tillbaka till huvudslingan.

Om allt annat misslyckas får du varna användaren att koden kommer vara långsam och visa en förloppsindikator. De kommer inte vara så glada som om du bara presenterat resultaten, men de vet i alla fall att programmet inte har kraschat och att de kan gå och ta sig en kopp kaffe.