Kapitel 7 Kapitel 7
Effektivitet inline eller inte?
Temporärer Minnespooler
Kapitel 7 - Effektivitet
• inline
• När bör man / bör man inte använda inline
• Optimering med inline
• Temporära objekt
• Minneshantering med minnespool - specialiserad new och delete
Effektivitet
Innehållet i denna del är inspirerat av
• Scott Meyers, ”Effective C++”
• Boken nämner 50 specifika tips om hur man bör skriva C++-program
samt
• Mayhew och Bulka, ”Efficient C++:
performance programming techniques''
• Boken jämför hastigheten hos olika konstruktioner i C++
Kompileringsprocessen Kompileringsprocessen
Kompilering
Länkning
Kompilering till objektfiler
• En källkodsfil (med suffix .cpp) som är körd genom preprocessorn kallas en
översättningsenhet (translation unit)
• I C++ kompileras källkodsfilerna separat till objektfiler (med suffix .o)
• Kompilatorn kan inte veta om anrop sker till funktion som inte är definierad.
• Länkaren har översikt över alla definierade funktioner och varnar om anrop sker till funktion som ej är definierad.
• Objektfilerna länkas sedan till ett körbart program.
Länkning av objektfiler
Inline Inline
När är inline olämpligt?
90-10-regeln Felhantering
Optimering
• Allmänt om optimering Regel nr 1: "Don't do it"
Regel nr 2: "Don't do it yet"
• Omöjligt att veta vilka delar som är tidskritiska
• Endast tidtagning avslöjar vilka delar som ska optimeras, om ens då…
Prestanda beror ju även på indata
inline
• inline är ett förslag till kompilatorn att ersätta funktionsanrop med den kod som anropas
• Man kan tänka sig inline som att kompilatorn klipp-och-klistrar kod
• inline kan vara till hjälp när man vill skriva effektiva program
• Utan inline kan man använda
preprocessorn för att uppnå samma effekt.
Man får då inte samma typkontroll
Lämplighet hos inline-definitioner:
• Funktioner med lång exekveringstid (många gånger tiden för anrop) är inte lämpliga för inline
• Funktioner som är rekursiva kan vara olämpliga, liksom funktioner med statiska variabler
• Funktionspekare till en funktion som deklarerats inline skapar en funktion i minnet som man kan peka till
inline eller inte?
inline eller inte?
När bör man undvika att använda inline?
• Använd inline där du lokaliserat en flaskhals
• Överdrivet användande ger märkbart större exekverbara program pga expansion av koden (code bloat)
• inline kan ge långsammare kod pga sidfel (page faults)
inline eller inte?
När bör man undvika att använda inline?
• inline kan öka kompileringstiderna
eftersom programkoden ligger i headerfilerna
• Använda objekt måste finnas definierade
• Ändringar ger omkompilering pga beroenden
char foo(int i) {
return i % 7;
}
// utan inline och utan optimering int i = 9;
char c = "abcdefg"[foo(i)];
// med inline och optimering int i = 9;
char c = 'c';
Exempel på snabbare program med inline:
Optimering mellan funktionsanrop är svårt, med inline blir det lättare
inline inline och 90-10-regeln
90-10 regeln (även känd som 80-20-regeln):
• De flesta program spenderar 90% av körtiden i 10% av koden
• De funktioner som utför arbetet vill man snabba upp
• Felhanteringkod används sällan och bör inte göras inline
inline vid behov
void foo() { if(error) {
/* 500 rader felhantering */
}
/* 10 rader kod */
}
for(int i = 0; i < 100000; i++) foo(); // foo är tidskritisk /* foo används på flera andra ställen */
Betrakta följande exempel: Vi konstaterar följande:
• Funktionen foo är tidskritisk
• Gör vi foo() inline får vi en stor ökning av kodens storlek pga felhanteringsrutinen Vi väljer att dela upp foo:
• Den del av koden som ska exekvera snabbt gör vi inline
• Felhanteringen låter vi ske genom funktionsanrop
inline vid behov
inline // vi gör foo inline void foo() {
if(error) {
foo_handle_error(); // ej inlineanrop }
/* 10 rader kod */
}
void foo_handle_error() { // felhanteringen ej inline /* 500 rader felhantering */
}
for(int i = 0; i < 100000; i++)
foo(); // foo expanderar till < 15 rader kod
Vårt nya, effektiva program blir:
inline vid behov
Temporära objekt Temporära objekt
Onödiga instruktioner Kopiering kontra
initiering Referenser
Temporära objekt och kopiering
För att slippa onödiga instruktioner vill man undvika att skapa temporära objekt:
• Vid anrop: skicka referenser istället för objekt där så är möjligt.
• Omformulera uttryck för att undvika temporära objekt.
Att slippa temporära objekt:
• Använd initiering istället för tilldelning, där så är möjligt
• Använd referenser för att undvika call- by-value
• Var medveten om operatoioner som skapar temporärer
Temporära objekt och
kopiering
std::string s;
std::string r("foo");
s = r + "bar"; // temp. objekt som innehåller "foobar"
s += r; // bättre: inget temp. objekt s += "bar"; // samma resultat
Exempel på hur man undviker temporära objekt:
Temporära objekt och kopiering
Annat, vanligt förekommande, exempel på onödiga instruktioner
c l a s s H u g e {
H u g e ( ) : e ( 0 ) { h e a v y ( ) ; } H u g e ( i n t i ) : e ( i ) { h e a v y ( ) ; } H u g e & o p e r a t o r = ( c o n s t H u g e & h ) { e = h . e ; h e a v y ( ) ; r e t u r n * t h i s ; }
h e a v y ( ) { f o r ( i n t i = 0 ; i < 1 0 0 0 0 0 0 0 ; i + + ) ; } i n t e ;
} ;
c l a s s A { H u g e e ;
A ( ) { e = 7 ; } / / k ö r h e a v y t r e g å n g e r A ( i n t ) : e ( 7 ) { } / / k ö r h e a v y e n g å n g } ;
Temporära objekt och kopiering
Specialiserad minneshantering Specialiserad minneshantering
De globala operatorerna ::new
och ::delete Minnespool för dynamisk allokering
Specialiserad minneshantering
• De inbyggda new och delete är byggda för att klara samtliga krav på minnesallokering.
• Eftersom de är generella kan de vara för långsamma i vissa sammanhang.
• Man kan då skapa specialbyggda new
och delete som sköter allokeringen.
• Om man ska allokera många objekt av samma typ kan man minska körtiderna genom att specialisera minnes-
allokeringen:
• Vi använder den inbyggda new för att allokera stora mängder minne; denna mängd kallar vi vår minnespool.
• Minnet delas sedan ut i lagom stora portioner i vår egen new.
Minnespool
/* Fraction definierar inte new och delete */
class Fraction {...};
// ineffektivt att allokera 5M objekt ett i taget for(int i = 0; i < 5000000; i++)
{
Fraction *f = new Fraction;
foo(f);
delete f;
}
Vi har följande program som vi vill optimera:
Minnespool - huvudfil
Minnespool
Vi skapar en mallklass Pool:
• Hanterar minnespoolen
• Allokerar större stycken minne för prestanda
• Styckar minne och delar ut till objekt
• Allockerar mer minne vid behov
• Länkar lediga objekt genom att återanvända det minne som objektet ska uppta
/ / i f i l e n p o o l . h t e m p l a t e < c l a s s T >
c l a s s P o o l { p u b l i c : P o o l ( ) ; ~ P o o l ( ) ;
v o i d * a l l o c ( ) ; / / h ä m t a e n m i n n e s c e l l v o i d f r e e ( v o i d * ) ; / / l ä m n a t i l l b a k a m i n n e s c e l l p r o t e c t e d :
v o i d e x p a n d ( ) ; / / u t ö k a m i n n e t
e n u m { E X P A N D = 6 5 5 3 6 } ; / / a l l o k e r a d s t o r l e k i b y t e s . . .
s t d : : v e c t o r < T * > b l o c k s ; / / b l o c k m e d m i n n e
s t r u c t L i s t { / / l ä n k a d l i s t a a v c e l l e r L i s t * n e x t ; / / p e k a r e t i l l n ä s t a c e l l } f r e e l i s t ; / / l e d i g a c e l l e r } ;
# i n c l u d e " p o o l . i n l " / / t e m p l a t e k l a s s e n s i m p l e m .
Minnespool - headerfil
/ / i f i l e n p o o l . i n l t e m p l a t e < c l a s s T >
P o o l < T > : : P o o l ( ) : b l o c k s ( 1 0 0 ) {
a s s e r t ( s i z e o f ( T ) > = s i z e o f ( P o o l < T > * ) ) ; f r e e l i s t . n e x t = 0 ;
e x p a n d ( ) ; }
t e m p l a t e < c l a s s T >
P o o l < T > : : ~ P o o l ( ) {
s t d : : v e c t o r < T * > : : i t e r a t o r i t ;
f o r ( i t = b l o c k s . b e g i n ( ) ; i t ! = b l o c k s . e n d ( ) ; i t + + ) d e l e t e [ ] * i t ;
}
t e m p l a t e < c l a s s T >
i n l i n e
v o i d * P o o l < T > : : a l l o c ( ) {
i f ( ! f r e e l i s t . n e x t ) e x p a n d ( ) ;
L i s t * t m p = f r e e l i s t . n e x t ; f r e e l i s t . n e x t = t m p - > n e x t ; r e t u r n r e i n t e r p r e t _ c a s t < v o i d * > ( t m p ) ; }
Minnespool - implementation Minnespool - implementation
/ / i f i l e n p o o l . i n l t e m p l a t e < c l a s s T >
i n l i n e
v o i d P o o l < T > : : f r e e ( v o i d * p ) {
L i s t * t m p = r e i n t e r p r e t _ c a s t < L i s t * > ( p ) ; t m p - > n e x t = f r e e l i s t . n e x t ;
f r e e l i s t . n e x t = t m p ; }
t e m p l a t e < c l a s s T >
v o i d P o o l < T > : : e x p a n d ( ) {
i n t s i z e = E X P A N D / s i z e o f ( T ) ; T * m e m = n e w T [ s i z e ] ; b l o c k s . p u s h _ b a c k ( m e m ) ;
L i s t * o l d p = r e i n t e r p r e t _ c a s t < L i s t * > ( m e m ) ; o l d p - > n e x t = f r e e l i s t . n e x t ;
f o r ( i n t i = 0 ; i < s i z e - 1 ; i + + ) { L i s t * n e w p =
r e i n t e r p r e t _ c a s t < L i s t * > ( m e m + i + 1 ) ; n e w p - > n e x t = o l d p ;
o l d p = n e w p ; }
f r e e l i s t . n e x t = o l d p ; }
Minnespool - testklass
class Fraction {
public:
Fraction(int d = 0, int n = 1) : n_(n), d_(d) {}
void *operator new(size_t size) { return pool.alloc(); }
void operator delete(void *p, size_t) { pool.free(p); }
protected:
int n_; // nominator int d_; // denominator ...
protected:
static Pool<Fraction> pool; // deklaration };