september 2011 Archieven
Performance: volgorde van condities
Geplaatst door martijn_brekhof op 2011-09-19 15:17:20 | Permanente link | Categorie: Programmeren | Reacties: 0
Er zijn een heleboel zaken die de performance van een programma kunnen beïnvloeden. Eén daarvan is je code en simpelweg geldt dat hoe minder instructies nodig zijn om iets uit te voeren hoe sneller het programma zal draaien. In deze en toekomstige blogs zal ik wat voorbeelden laten zien waarbij de performance soms iets en soms aanzienlijk verbeterd kan worden door de code net iets anders op te schrijven. Ik gebruik voor de voorbeelden de programmeertaal C. Echter zijn de meeste concepten ook in andere talen van toepassing. Let op, soms zal het herschrijven van de code tot gevolg hebben dat het niet meer voldoet aan een gebruikte programmeerstijl of standaard. Daar maak ik me even geen zorgen over. Het gaat me nu vooral om de performance.
In deze eerste blog omtrent performance wil ik het hebben over de volgorde van if-then-else statements.
Bekijk het volgende stukje C-code eens.
#include<stdio.h>
#define AMOUNT_OF_ITERATIONS 1000000000
int func1() { return 10; }
int func2() { return 12; }
int func3() { return 23; }
int func4() { return 11; }
int parse_message(char *msg)
{
if(strcmp("potato", msg) == 0)
{
return func1();
}
else if(strcmp("tomato", msg) == 0)
{
return func2();
}
else if(strcmp("banana", msg) == 0)
{
return func3();
}
else if(strcmp("orange", msg) == 0)
{
return func4();
}
}
int main(int argc, char* argv[])
{
unsigned long int i;
int rate;
for(i = 0; i < AMOUNT_OF_ITERATIONS; i++)
{
if( random()%2 )
rate = parse_message(argv[1]);
else
rate = parse_message(argv[2]);
}
printf("Rate=%d\n", rate);
return 0;
}
De code doet niet echt veel zinnigs. Het is een aangepaste versie van code
van een daemon waarin zich een performance probleem voordeed. Maar dit soort
code, waarbij op basis
van een reeks if-then-else statements (of in een switch) bepaalde code wel
of niet wordt aangeroepen, kom ik
geregeld tegen.
De main functie is alleen bedoeld om het effect van de volgorde van de
condities in de functie parse_message te demonstreren. Dat we hier op
"willekeurige" basis bepalen of het eerste of het tweede argument moet worden
gebruikt is nodig om te voorkomen dat compiler-optimalisaties roet in het eten
gooien.
De code is als volgt te compileren met gcc.
$ gcc -O3 freqs.c -o freqs
De optie -O3 zorgt ervoor dat gcc zoveel mogelijk optimalisatie zal
toepassen. Dit doe ik om te laten zien dat je als programmeur toch ook echt
invloed kan hebben.
Het programma kan ik als volgt uitvoeren.
$ time ./freqs orange orange
Rate=11
real 0m15.747s
user 0m15.741s
sys 0m0.000s
Ik gebruik het programma time om de hoeveelheid tijd te laten zien die het
programma in beslag heeft genomen.
Ik roep het programma dus aan met twee keer "orange" als argument. Dit zorgt
ervoor dat
altijd de laatste conditie slaagt in de functie parse_message.
Laten we
nu eens het programma uitvoeren met als argumenten potato en potato:
$ time ./freqs potato potato
Rate=10
real 0m6.231s
user 0m6.228s
sys 0m0.000s
Dit is een performance verbetering van maar liefst 60 procent.
De winst wordt behaald doordat in de tweede run altijd de eerste conditie
slaagt.
Bij de eerste run slaagde telkens de laatste conditie en werd dus tijdens
iedere
aanroep van parse_message ook gecontroleerd of message gelijk is aan
potato, tomato, of banana. Deze controle wordt in de tweede run niet
gedaan waardoor deze dus vele malen sneller is.
Vaak zullen condities met een bepaalde frequentie slagen gedurende normaal
gebruik van het programma.
Stel nu dat uit tests blijkt dat het programma voor 90 procent wordt
aangeroepen met orange en tomato. Dan zal het dus lonen om de condities
voor
orange en tomato bovenaan te zetten.
Het analyseren van het slagingspercentage van de verschillende condities wordt
ook wel een
frequentie-analyse genoemd.
Soms kun je direct uit de programmacode de frequenties afleiden. Maar meestal zul je met
een zogenaamde
coverage tool of profiler aan de slag moeten. Je voert dan het programma in
een realistische omgeving uit en de coverage tool of profiler laat dan zien
hoe vaak bepaalde onderdelen van je code zijn uitgevoerd.
Om dit te illustreren zal ik de GNU profiler gebruiken, genaamd gprof.
Om gprof te kunnen gebruiken dien je het programma te hercompileren met
profile informatie (gcc optie -pg) als volgt:
$ gcc -pg freqs.c -o freqs
Let op dat je geen level 3 optimalisatie meegeeft. Je zult merken dat anders de
functies niet meer worden aangeroepen, ze zijn namelijk inline gecompileerd in de main functie. Het gevolg is dat je dan geen frequentie-analyse kan doen.
Door het programma nogmaals uit te voeren wordt er profiling
informatie naar het bestand gmon.out geschreven.
$ ./freqs tomato banana
Met gprof kun je vervolgens het bestand gmon.out uitlezen.
$ gprof -b -Q freqs
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
67.68 1.78 1.78 main
26.62 2.48 0.70 100000000 7.00 8.35 parse_message
3.04 2.56 0.08 49996838 1.60 1.60 func2
2.09 2.62 0.06 50003162 1.10 1.10 func3
0.57 2.63 0.01 func1
Voor iedere functie wordt de rekentijd en het aantal aanroepen getoond. Voor
ons is nu alleen het aantal aanroepen van belang. Dit wordt getoond in de
kolom calls. Vervolgens kun je deze informatie gebruiken om de beste
volgorde van je if-statements te bepalen. In bovenstaand geval zouden dus
eerst de condities voor "tomato" en "banana" moeten komen en dan de overige.
Wanneer je geen functies aanroept in je if-statements kun je beter gcov
gebruiken. Als je toch gprof wilt gebruiken kun je dummy-functies toevoegen
aan je code.
void func1() { return; }
void func2() { return; }
...
Deze functies dien je vervolgens in je if-statements aan te roepen.
Helaas levert frequentie-analyse niet altijd een performance winst
op. Dat het in bovenstaand voorbeeld wel winst oplevert komt voor het grootste
deel door de functie strcmp. Deze routine vereist nogal wat CPU tijd. Wanneer de condities
een simpele vergelijking bevatten (bijvoorbeeld amount == 1) zul je merken
dat de winst erg tegenvalt.
Daarnaast moet er uit de frequentie analyse een duidelijke winnaar komen. Als
de condities ongeveer met dezelfde frequentie slagen zal de
winst ook tegenvallen.
Geluiden uit de keukens van Debian en Fedora (augustus)
Geplaatst door martijn_brekhof op 2011-09-09 12:35:22 | Permanente link | Categorie: Systeembeheer | Reacties: 0
Bij Debian meldt Roger Leigh dat Fedora een aparte groep "lock" heeft
geïntroduceerd
om de directory /var/lock niet meer world-writable te hoeven maken. Hij vraagt
zich af of dit onder Debian ook zo kan worden opgezet.
Het zal vooral
aanpassingen aan de init-scripts vereisen waarbij de opgestarte daemon niet
onder root-rechten zal draaien. De benodigde lock directory moet dan eerst
aangemaakt worden en van de juiste permissies worden voorzien voordat de
daemon wordt opgestart (link).
Asheesh Laroia laat weten dat de nieuwe mentor website up is (link). De site is bedoeld om het makkelijker te maken voor niet officiële Debian ontwikkelaars om een zogenaamde sponsor te vinden voor hun package. De sponsor zal het pakket dan controleren en namens de ontwikkelaar in de Debian distributie opnemen.
Jan Möbius heeft een bug aangemeld met betrekking tot rpc.statd dat
poort 631 in gebruik neemt terwijl dit de poort is voor CUPS.
Ben Hutchings laat weten dat het een bekend probleem is met het gebruik van
de bindresvport
functie. Dit is een functie die een willekeurige poort opent tussen 512 en 1023.
Hij maakt vervolgens meteen gebruik van de gelegenheid om systemd te promoten door te stellen dat
overstappen op systemd de juiste oplossing is. Deze oplossing wordt snel van tafel
geveegd. Er is namelijk de mogelijkheid om bepaalde poorten uit te sluiten voor
bindresvport. Echter wordt dit voor de IPv6 versie van bindresvport niet
gebruikt. Ben Hutchings levert vervolgens een patch aan om dit op te lossen
(link, link).
Er wordt druk gediscussieerd over het nut van xz compressie ten
opzichte van de veel gebruikte gz compressie voor de bestanden in de
directory /usr/share/doc. Het blijkt dat de winst
van xz ten opzichte van de al veel gebruikte bz2 compressie minimaal is en
soms zelfs slechter (link). Er wordt dan ook geopperd om wellicht bz2 te gaan
gebruiken in plaats van over te stappen op xz.
Nanakos Chrysostomos heeft na het aanbrengen van een patch voor een lang openstaande bug zichzelf als copyrighthouder voor de yubico PAM module toegevoegd. Hier was de originele auteur niet van gediend. Nanakos vroeg zich af of dit correct was. Hij kreeg helaas geen bijval en iedereen vond zijn bijdrage ook te klein om als copyright houder te worden opgenomen (link).
Bij Fedora meldt Josef Bacik in navolging van de heftige discussie rondom BTRFS vorige maand dat BTRFS niet het standaard filesysteem wordt in Fedora Core 16 (link).
