49 votes

Pourquoi l'optimisation tue-t-elle cette fonction?

Récemment, nous avons eu une conférence à l'université sur la programmation spéciaux dans plusieurs langues. Il a écrit la fonction suivante:

inline u64 Swap_64(u64 x)
{
    u64 tmp;
    (*(u32*)&tmp)       = Swap_32(*(((u32*)&x)+1));
    (*(((u32*)&tmp)+1)) = Swap_32(*(u32*) &x);

    return tmp;
}

Alors que je comprends tout à fait que c'est vraiment aussi mauvais style, en termes de lisibilité, de son point principal est que cette partie de code a bien fonctionné dans le code de production jusqu'à ce qu'ils ont permis à un haut niveau d'optimisation. Ensuite, le code serait tout simplement ne rien faire.

Il a dit que tous les devoirs de la variable tmp serait optimisé par le compilateur. Mais pourquoi cela se produit?

Je comprends qu'il y a des circonstances où les variables doivent être déclarées volatile de sorte que le compilateur ne pas y toucher, même si il pense qu'ils ne sont jamais lu ou écrit, mais je ne sais pas pourquoi ce qui se passerait ici.

47voto

Jarod42 Points 15729

En C ++, les arguments de pointeur sont supposés ne pas alias (à l'exception de char* ) s'ils pointent vers des types fondamentalement différents ( règles de "repliement strict" ). Cela permet certaines optimisations.

Ici, u64 tmp n'est jamais modifié en tant que u64 .
Une teneur de u32* est modifiée mais peut ne pas avoir de relation avec ' u64 tmp ' et peut donc être considérée comme nop pour u64 tmp .

41voto

Shafik Yaghmour Points 42198

Ce code viole la stricte aliasing règles qui rend illégal le fait d'accéder à un objet par l'intermédiaire d'un pointeur de type différent, bien que l'accès par le biais d'un char * est autorisé. Le compilateur est permis de supposer que les pointeurs de types différents ne désignent pas la même mémoire et d'optimiser en conséquence. Cela signifie également que le code appelle un comportement indéfini , et peut vraiment faire n'importe quoi.

L'une des meilleures références pour cette rubrique est la Compréhension Stricte Aliasing et nous pouvons voir le premier exemple est dans la même veine que celui des OP code:

uint32_t swap_words( uint32_t arg )
{
  uint16_t* const sp = (uint16_t*)&arg;
  uint16_t        hi = sp[0];
  uint16_t        lo = sp[1];

  sp[1] = hi;
  sp[0] = lo;

 return (arg);
} 

L'article explique ce code viole stricte aliasing règles depuis sp est un alias de arg , mais ils ont des types différents et dit que, bien que de compiler, il est probable arg sera inchangée après l' swap_words de rendement. Bien qu'avec de simples tests, je suis incapable de reproduire ce résultat avec le code ci-dessus, ni l'OPs code mais qui ne veut rien dire puisque c'est un comportement indéfini et donc pas prévisible.

L'article continue à parler de nombreux cas différents et présente plusieurs solution de travail, y compris de type beaucoup les jeux de mots par l'intermédiaire d'un syndicat, ce qui est bien définie dans C991 et peut-être pas définie en C++ , mais dans la pratique, est pris en charge par la plupart des compilateurs, par exemple, ici, est de gcc référence de type beaucoup les jeux de mots. Le thread précédent Objectif de Syndicats en C et C++ va dans les détails sanglants. Bien qu'il existe de nombreux fils sur ce sujet, ce qui semble faire le meilleur travail.

Le code pour que la solution est comme suit:

typedef union
{
  uint32_t u32;
  uint16_t u16[2];
} U32;

uint32_t swap_words( uint32_t arg )
{
  U32      in;
  uint16_t lo;
  uint16_t hi;

  in.u32    = arg;
  hi        = in.u16[0];
  lo        = in.u16[1];
  in.u16[0] = lo;
  in.u16[1] = hi;

  return (in.u32);
}

Pour référence la section pertinente de la C99 projet de norme sur le strict aliasing est - 6.5 Expressions paragraphe 7 , qui dit:

Un objet doit avoir sa valeur stockée et accessible uniquement par une lvalue expression qui est l'un des types suivants:76)

- un type compatible avec l'efficacité du type de l'objet,

- une version qualifiée d'un type compatible avec l'efficacité du type de l'objet,

- un type qui est signé ou non signé de type correspondant à l'effectif type de la objet,

- un type qui est signé ou non signé de type correspondant à une version qualifiée de l' efficace type de l'objet,

- une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus parmi ses les membres (y compris, de manière récursive, un membre d'un subaggregate ou contenus de l'union), ou

- un type de caractère.

et la note de bas de page 76 dit:

Le but de cette liste est de préciser les circonstances dans lesquelles un objet peut ou peut ne pas être un alias.

et la section pertinente du C++ avant-projet de norme est - 3.10 Lvalues et rvalues paragraphe 10

L'article de Type beaucoup les jeux de mots et strict-aliasing donne un plus doux mais moins complète introduction du sujet et C99 revisitée propose une analyse profonde de C99 et de l'aliasing et n'est pas la lumière de la lecture. Cette réponse à l'Accès inactif membre de l'union indéfinis? va sur les fonds vaseux des détails de la beaucoup les jeux de mots par l'intermédiaire d'un syndicat en C++ et n'est pas la lumière d'une lecture.


Notes de bas de page:

  1. Citant le commentaire de Pascal Cuoq: [...]C99 qui a d'abord été maladroitement formulée, apparaissant à faire de type beaucoup les jeux de mots par le biais des syndicats indéfini. En réalité, type beaucoup les jeux de mots bien que les syndicats est légal en C89, juridique en C11, et c'était légal en C99 tout le long bien qu'il a fallu attendre 2004 pour le comité de corriger la formulation incorrecte, et la libération subséquente de TC3. open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm

10voto

pepper_chico Points 2428

g ++ (Ubuntu / Linaro 4.8.1-10ubuntu9) 4.8.1:

 > g++ -Wall -std=c++11 -O0 -o sample sample.cpp

> g++ -Wall -std=c++11 -O3 -o sample sample.cpp
sample.cpp: In function ‘uint64_t Swap_64(uint64_t)':
sample.cpp:10:19: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     (*(uint32_t*)&tmp)       = Swap_32(*(((uint32_t*)&x)+1));
                   ^
sample.cpp:11:54: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     (*(((uint32_t*)&tmp)+1)) = Swap_32(*(uint32_t*) &x);
                                                      ^
 

Clang 3.4 ne met en garde dans aucun niveau d'optimisation, ce qui est curieux ...

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