46 votes

gcc -O0 optimise toujours le code "non utilisé". Y at-il un drapeau de compilation pour changer cela?

Comme je l'ai mis dans cette question, gcc est la suppression (oui, avec -O0) une ligne de code _mm_div_ss(s1, s2); sans doute parce que le résultat n'est pas enregistré. Toutefois, cela devrait déclencher une exception de virgule flottante et d'élever SIGFPE, qui ne peut pas se produire si l'appel est supprimé.

Question: Est-il d'un drapeau, d'un ou de plusieurs indicateurs, pour passer à gcc de sorte que le code est compilé en tant que-est? Je suis en train de penser à quelque chose comme fno-remove-unused mais je ne vois pas de quoi que ce soit. Dans l'idéal, ce serait un compilateur drapeau au lieu d'avoir à changer mon code source, mais si que n'est pas pris en charge est-il un gcc attribut/pragma à utiliser à la place?

Les choses que j'ai essayé:

$ gcc --help=optimizers | grep -i remove

pas de résultats.

$ gcc --help=optimizers | grep -i unused

pas de résultats.

Et explicitement la désactivation de tous les morts de code d'élimination des drapeaux -- notez qu'il n'y a pas d'avertissement à propos de code inutilisé:

$ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline \
     -fno-dce -fno-dse -fno-tree-dce \
     -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse  \
     -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop \
     -fno-tree-builtin-call-dce -fno-tree-cselim a.c
a.c: In function ‘main':
a.c:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]
     __m128 s1, s2;
     ^
$

Le programme Source

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <xmmintrin.h>

static void sigaction_sfpe(int signal, siginfo_t *si, void *arg)
{
    printf("%d,%d,%d\n", signal, si!=NULL?1:0, arg!=NULL?1:0);
    printf("inside SIGFPE handler\nexit now.\n");
    exit(1);
}

int main()
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = sigaction_sfpe;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGFPE, &sa, NULL);

    _mm_setcsr(0x00001D80);

    __m128 s1, s2;
    s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0);
    s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0);
    _mm_div_ss(s1, s2);

    printf("done (no error).\n");

    return 0;
}

La compilation du programme ci-dessus donne

$ ./a.out
done (no error).

La modification de la ligne

_mm_div_ss(s1, s2);

pour

s2 = _mm_div_ss(s1, s2); // add "s2 = "

produit le résultat attendu:

$ ./a.out
inside SIGFPE handler

Modifier avec plus de détails.

Cela semble être lié à l' __always_inline__ l'attribut dans l' _mm_div_ss définition.

$ cat t.c
int
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}


$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$  

(pas d'avertissements ou d'erreurs)

$ ./t.out
Floating point exception
$

vs ci-dessous (même à l'exception de la fonction d'attributs)

$ cat t.c
__inline int __attribute__((__always_inline__))
div(int b)
{
    return 1/b;
}

int main()
{
    div(0);
    return 0;
}

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
$   

(pas d'avertissements ou d'erreurs)

$ ./t.out
$

Ajout de la fonction d'attribut __warn_unused_result__ en donne au moins un message utile:

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out
t.c: In function ‘main':
t.c:9:5: warning: ignoring return value of ‘div', declared with attribute warn_unused_result [-Wunused-result]
     div(0);
     ^

edit:

D'un débat sur la gcc liste de diffusion. En fin de compte, je pense que tout fonctionne comme prévu.

32voto

Matthieu M. Points 101624

Pourquoi ne gcc émettent pas de la spécifié de l'enseignement?

Un compilateur produit du code qui doit avoir les comportements observables spécifiée par la Norme. Tout ce qui n'est pas observable peut être modifié et optimisé) à volonté, car il ne modifie pas le comportement du programme (comme indiqué).

Comment pouvez-vous battre dans la soumission?

L'astuce est de faire le compilateur crois que le comportement de la pièce, en particulier, de code est en réalité observable.

Depuis, c'est un problème fréquemment rencontré dans les micro-benchmark, je vous conseille de regarder comment (par exemple) Google-Référence porte sur cette question. D' benchmark_api.h , nous obtenons:

template <class Tp>
inline void DoNotOptimize(Tp const& value) {
    asm volatile("" : : "g"(value) : "memory");
}

Les détails de cette syntaxe sont ennuyeux, notre but, nous avons seulement besoin de savoir:

  • "g"(value) raconte qu' value est utilisé comme entrée pour la déclaration
  • "memory" est une compilation de lecture/écriture de la barrière

Donc, nous pouvons changer le code pour:

asm volatile("" : : : "memory");

__m128 result = _mm_div_ss(s1, s2);

asm volatile("" : : "g"(result) : );

Qui:

  • force le compilateur à considérer qu' s1 et s2 ont été modifiées entre leur initialisation et utilisation
  • force le compilateur à considérer que le résultat de l'opération est utilisé

Il n'est pas nécessaire pour n'importe quel drapeau, et il devrait fonctionner à n'importe quel niveau de l'optimisation (je l'ai testé sur https://gcc.godbolt.org/ au -O3).

23voto

Art Points 6040

GCC n'est pas "optimiser" quoi que ce soit ici. Il n'a tout simplement pas générer de code inutile. Il semble très commun de l'illusion qu'il y a une forme pure de code que le compilateur doit générer, et toutes les modifications qui sont une "optimisation". Il n'y a pas une telle chose.

Le compilateur crée une certaine structure de données qui représente ce que le code de moyens, alors elle s'applique certaines transformations sur les données de la structure et à partir de ce qu'il génère assembleur qui obtient ensuite compilé vers le bas pour les instructions. Si vous compilez sans "optimisations", ça veut simplement dire que le compilateur ne faire le moins d'efforts possible de générer du code.

Dans ce cas, l'intégralité de la déclaration est inutile, car elle ne fait rien et est jeté immédiatement (après l'extension de la inlines et ce que les objets internes dire qu'il est équivalent à l'écriture a/b;, la différence c'est que l'écriture a/b; émet un avertissement sur statement with no effect tandis que les objets internes ne sont probablement pas gérées par les mêmes mises en garde). Ce n'est pas de l'optimisation, le compilateur aurait en fait de consacrer des efforts supplémentaires pour inventer du sens à une déclaration vide de sens, puis de faux une variable temporaire pour stocker le résultat de cette instruction pour le jeter ensuite.

Ce que vous cherchez n'est pas de drapeaux pour désactiver les optimisations, mais pessimization drapeaux. Je ne pense pas que tout les développeurs de compilateurs perdre du temps à la mise en œuvre de ces drapeaux. Autre que peut-être comme un poisson d'avril blague.

10voto

deniss Points 2705

Je ne suis pas un expert en gcc internes, mais il semble que votre problème n'est pas avec la suppression du code mort par certains d'optimisation de passe. Il est plus que probable que le compilateur n'est pas même d'envisager de générer ce code dans la première place.

Nous allons réduire votre exemple de compilateur spécifique intrinsèques pour un simple vieux plus:

int foo(int num) {
    num + 77;
    return num + 15;
}

Pas de code pour + 77 généré:

foo(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        add     eax, 15
        pop     rbp
        ret

Lorsque l'un des opérandes a des effets secondaires, seulement que l'opérande obtient évalué. Encore, pas de plus dans l'assemblée.

Mais l'enregistrement de ce résultat dans un (même non utilisé) variable force le compilateur à générer du code pour l'ajout:

int foo(int num) {
  int baz = num + 77;
  return num + 15;
}

Assemblée:

foo(int):
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-20], edi
    mov     eax, DWORD PTR [rbp-20]
    add     eax, 77
    mov     DWORD PTR [rbp-4], eax
    mov     eax, DWORD PTR [rbp-20]
    add     eax, 15
    pop     rbp
    ret

Ce qui suit est juste une spéculation, mais de mon expérience avec le compilateur de la construction, il est plus naturel de ne pas générer le code pour les expressions, plutôt que d'éliminer ce code plus tard.

Ma recommandation est d'être clair sur vos intentions, et de mettre le résultat d'une expression dans volatils (et, par conséquent, non-amovible par l'optimiseur) de la variable.

@Matthieu M a souligné qu'il n'est pas suffisant pour empêcher precomputing la valeur. Donc pour quelque chose de plus que de jouer avec les signaux, vous devez utiliser documenté façons d'effectuer l'exacte instruction que vous voulez (probablement, volatile assembly en ligne).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X