Ho notato per la prima volta nel 2009 che GCC (almeno sui miei progetti e sulle mie macchine) ha la tendenza a generare un codice notevolmente più veloce se ottimizzo per dimensioni ( -Os) anziché velocità ( -O2o -O3), e mi chiedo da allora perché.
Sono riuscito a creare un codice (piuttosto stupido) che mostra questo comportamento sorprendente ed è sufficientemente piccolo per essere pubblicato qui.
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int add(const int& x, const int& y) {
return x + y;
}
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
Se lo compilo -Os, ci vogliono 0,38 secondi per eseguire questo programma e 0,44 secondi se è compilato con -O2o -O3. Questi tempi sono ottenuti in modo coerente e praticamente senza rumore (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).
(Aggiornamento: ho spostato tutto il codice assembly in GitHub : hanno reso il post gonfio e apparentemente aggiungono molto poco valore alle domande poiché le fno-align-*bandiere hanno lo stesso effetto.)
Ecco l'assieme generato con -Ose -O2.
Sfortunatamente, la mia comprensione dell'assemblaggio è molto limitata, quindi non ho idea se ciò che ho fatto dopo sia stato corretto: ho afferrato l'assemblaggio per -O2e ho unito tutte le sue differenze nell'assemblaggio per -Os tranne le .p2alignlinee, risultato qui . Questo codice funziona ancora in 0.38s e l'unica differenza è la .p2align roba.
Se immagino correttamente, si tratta di imbottiture per l'allineamento dello stack. Secondo Perché il pad GCC funziona con i NOP? viene fatto nella speranza che il codice venga eseguito più velocemente, ma a quanto pare questa ottimizzazione è fallita nel mio caso.
È l'imbottitura che è il colpevole in questo caso? Perché e come?
Il rumore che produce praticamente rende impossibili le microottimizzazioni temporali.
Come posso assicurarmi che tali allineamenti fortunati / sfortunati accidentali non interferiscano quando eseguo microottimizzazioni (non correlate all'allineamento dello stack) sul codice sorgente C o C ++?
AGGIORNARE:
Seguendo la risposta di Pascal Cuoq ho armeggiato un po 'con gli allineamenti. Passando -O2 -fno-align-functions -fno-align-loopsa gcc, tutti .p2alignscompaiono dall'assembly e l'eseguibile generato viene eseguito in 0.38s. Secondo la documentazione di gcc :
-Os abilita tutte le ottimizzazioni -O2 [ma] -Os disabilita i seguenti flag di ottimizzazione:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
Quindi, sembra quasi un problema di allineamento (errato).
Sono ancora scettico riguardo a -march=nativequanto suggerito nella risposta di Marat Dukhan . Non sono convinto che non stia solo interferendo con questo (mis) problema di allineamento; non ha assolutamente alcun effetto sulla mia macchina. (Tuttavia, ho votato a favore della sua risposta.)
AGGIORNAMENTO 2:
Possiamo togliere -Osla foto. I seguenti tempi sono ottenuti compilando con
-O2 -fno-omit-frame-pointer0.37s-O2 -fno-align-functions -fno-align-loops0.37s-S -O2quindi spostando manualmente il gruppoadd()dopowork()0,37 secondi-O20.44s
Mi sembra che la distanza add()dal sito di chiamata sia molto importante. Ho provato perf, ma l'output di perf state perf reportha poco senso per me. Tuttavia, ho potuto ottenere solo un risultato coerente da esso:
-O2:
602,312,864 stalled-cycles-frontend # 0.00% frontend cycles idle
3,318 cache-misses
0.432703993 seconds time elapsed
[...]
81.23% a.out a.out [.] work(int, int)
18.50% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
100.00 ¦ lea (%rdi,%rsi,1),%eax
¦ }
¦ ? retq
[...]
¦ int z = add(x, y);
1.93 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
79.79 ¦ add %eax,%ebx
Per fno-align-*:
604,072,552 stalled-cycles-frontend # 0.00% frontend cycles idle
9,508 cache-misses
0.375681928 seconds time elapsed
[...]
82.58% a.out a.out [.] work(int, int)
16.83% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
51.59 ¦ lea (%rdi,%rsi,1),%eax
¦ }
[...]
¦ __attribute__((noinline))
¦ static int work(int xval, int yval) {
¦ int sum(0);
¦ for (int i=0; i<LOOP_BOUND; ++i) {
¦ int x(xval+sum);
8.20 ¦ lea 0x0(%r13,%rbx,1),%edi
¦ int y(yval+sum);
¦ int z = add(x, y);
35.34 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
39.48 ¦ add %eax,%ebx
¦ }
Per -fno-omit-frame-pointer:
404,625,639 stalled-cycles-frontend # 0.00% frontend cycles idle
10,514 cache-misses
0.375445137 seconds time elapsed
[...]
75.35% a.out a.out [.] add(int const&, int const&) [clone .isra.0] ¦
24.46% a.out a.out [.] work(int, int)
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
18.67 ¦ push %rbp
¦ return x + y;
18.49 ¦ lea (%rdi,%rsi,1),%eax
¦ const int LOOP_BOUND = 200000000;
¦
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ mov %rsp,%rbp
¦ return x + y;
¦ }
12.71 ¦ pop %rbp
¦ ? retq
[...]
¦ int z = add(x, y);
¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
29.83 ¦ add %eax,%ebx
Sembra che ci stiamo bloccando sulla chiamata add()nel caso lento.
Ho esaminato tutto ciò che perf -epuò sputare sulla mia macchina; non solo le statistiche fornite sopra.
Per lo stesso eseguibile, stalled-cycles-frontendmostra una correlazione lineare con il tempo di esecuzione; Non ho notato nient'altro che sarebbe correlato così chiaramente. (Il confronto stalled-cycles-frontendper diversi eseguibili non ha senso per me.)
Ho incluso la cache mancante come è emerso come il primo commento. Ho esaminato tutte le mancate cache che possono essere misurate sulla mia macchina perf, non solo quelle indicate sopra. I mancati cache sono molto rumorosi e mostrano poca o nessuna correlazione con i tempi di esecuzione.