J'ai d'abord remarqué en 2009 que gcc (au moins sur mes projets et sur mes machines) ont tendance à générer beaucoup plus rapidement code si j'optimiser pour la taille (-Os
) au lieu de la vitesse (-O2
ou -O3
) et j'ai été vous demandez-vous jamais pourquoi depuis.
J'ai réussi à créer un (plutôt ridicule) de code qui montre ce comportement surprenant et est suffisamment petit pour être posté ici.
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;
}
Si je compile avec -Os
, il faut 0.38 s pour exécuter ce programme, et de 0,44 s si il est compilé avec -O2
ou -O3
. Ces temps sont obtenus de manière cohérente et avec pratiquement pas de bruit (gcc 4.7.2, x86_64 GNU/Linux, Intel Core i5-3320M).
(Mise à jour: j'ai déplacé l'ensemble de l'assemblée des codes de github: Ils ont fait le post de ballonnement et, apparemment, ajouter très peu d'intérêt pour les questions que l' fno-align-*
drapeaux ont le même effet.)
L'assembly généré avec -Os
et -O2
.
Malheureusement, ma compréhension de l'assemblée est très limité, de sorte que je n'ai aucune idée si ce que j'ai fait suivant est correct: j'ai attrapé l'assemblée pour -O2
et de fusion de toutes ses différences dans l'assemblée pour -Os
à l'exception de l' .p2align
lignes, le résultat ici. Ce code s'exécute toujours dans 0.38 s et la seule différence est l' .p2align
trucs.
Si je devine correctement, ce sont les rembourrages pour la pile de l'alignement. Selon Pourquoi ne GCC pad fonctions avec des Opr? c'est fait dans l'espoir que le code sera exécuté plus rapidement, mais, apparemment, cette optimisation est retournée dans mon cas.
Est-ce le rembourrage qui est le coupable dans cette affaire? Pourquoi et comment?
Le bruit qu'il fait assez bien fait de synchronisation micro-optimisations impossible.
Comment puis-je faire en sorte que cette accidentelle de la chance / malchance alignements ne sont pas interférer quand je fais des micro-optimisations (sans rapport avec la pile de l'alignement) sur C ou C++ codes sources?
Mise à JOUR:
Suite à Pascal Cuoq réponse j'ai bricolé un peu avec les alignements. En passant -O2 -fno-align-functions -fno-align-loops
de gcc, tous .p2align
sont partis de l'assemblée et l'exécutable généré s'exécute dans 0.38 s. Selon la doc de gcc:
-Os permet à tous -O2 optimisations [mais] -Os désactive la suite à l'optimisation des drapeaux:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
Donc, il semble comme une (mauvaise)problème d'alignement.
Je suis toujours sceptique sur -march=native
comme le suggère Marat Dukhan de réponse. Je ne suis pas convaincu que ce n'est pas seulement interférer avec cette (mauvaise)problème d'alignement; il n'a absolument aucun effet sur ma machine. (Néanmoins, je upvoted sa réponse.)
Mise à JOUR 2:
Nous pouvons prendre en -Os
hors de l'image. Les temps suivants sont obtenus par compilation avec
-O2 -fno-omit-frame-pointer
0.37 s-O2 -fno-align-functions -fno-align-loops
0.37 s-S -O2
puis de les déplacer manuellement l'assemblée de l'add()
aprèswork()
0.37 s-O2
De 0,44 s
Il ressemble pour moi à la distance de l' add()
à partir de l'appel site compte beaucoup. J'ai essayé perf
, mais la sortie de l' perf stat
et perf report
fait très peu de sens pour moi. Cependant, je ne pouvait obtenir un résultat cohérent:
-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
Pour 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
│ }
Pour -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
Il semble que nous caler sur l'appel à l' add()
dans le scénario de croissance lente.
J'ai examiné tout ce qu' perf -e
peut cracher sur ma machine, et non seulement les statistiques qui figurent ci-dessus.
Pour le même exécutable, l' stalled-cycles-frontend
montre de corrélation linéaire avec le temps d'exécution; je n'ai pas remarqué quelque chose d'autre qui serait en corrélation clairement. (En comparant stalled-cycles-frontend
pour les différents exécutables ne fait pas de sens pour moi.)
J'ai inclus la cache comme il est venu comme le premier commentaire. J'ai examiné tous les défauts de cache qui peuvent être mesurés sur ma machine par perf, pas seulement ceux donnés ci-dessus. Le cache sont très très bruyant et montrent peu ou pas de corrélation avec le temps de l'exécution.