c++ - Le compilateur est-il autorisé à optimiser les allocations de mémoire en tas?




gcc optimization (4)

Considérez le code simple suivant qui utilise new (je suis conscient qu'il n'y a pas de delete[] , mais cela ne concerne pas cette question):

int main()
{
    int* mem = new int[100];

    return 0;
}

Le compilateur est-il autorisé à optimiser le new appel?

Dans mes recherches, g ++ (5.2.0) et Visual Studio 2015 n'optimisent pas le new appel, contrairement à clang (3.0+) . Tous les tests ont été réalisés avec les optimisations complètes activées (-O3 pour g ++ et clang, mode de publication pour Visual Studio).

Est-ce que new ne fait pas un appel système sous le capot, rendant impossible pour un compilateur d’optimiser cela?

EDIT : J'ai maintenant exclu les comportements non définis du programme:

#include <new>  

int main()
{
    int* mem = new (std::nothrow) int[100];
    return 0;
}

Clang 3.0 n'optimise plus cette possibilité , mais les versions ultérieures le font .

EDIT2 :

#include <new>  

int main()
{
    int* mem = new (std::nothrow) int[1000];

    if (mem != 0)
      return 1;

    return 0;
}

Clang retourne toujours 1 .


Ceci est autorisé par N3664 .

Une implémentation est autorisée à omettre un appel à une fonction d'allocation globale remplaçable (18.6.1.1, 18.6.1.2). Dans ce cas, le stockage est plutôt fourni par la mise en oeuvre ou fourni en étendant l'allocation d'une autre nouvelle expression.

Cette proposition fait partie du standard C ++ 14. Ainsi, en C ++ 14, le compilateur est autorisé à optimiser une new expression (même si elle peut être renvoyée).

Si vous examinez l' état de la mise en œuvre de Clang, vous y verrez clairement la mise en œuvre de N3664.

Si vous observez ce comportement lors de la compilation en C ++ 11 ou C ++ 03, vous devez remplir un bogue.

Notez qu'avant C ++ 14, les allocations de mémoire dynamiques faisaient partie du statut observable du programme (bien que je ne puisse pas trouver de référence pour le moment), de sorte qu'une implémentation conforme n'était pas autorisée à appliquer la règle as-if dans cet exemple. Cas.


Gardez à l'esprit que la norme C ++ indique ce qu'un programme correct doit faire, pas comment il doit le faire. Cela ne se voit pas du tout, car de nouvelles architectures peuvent apparaître et sont apparues après l’écriture de la norme et que celle-ci doit leur être utile.

new ne doit pas nécessairement être un appel système sous le capot. Il existe des ordinateurs utilisables sans système d'exploitation et sans concept d'appel système.

Par conséquent, tant que le comportement final ne change pas, le compilateur peut optimiser tout et tout. Y compris cette new

Il y a une mise en garde.
Un opérateur mondial de remplacement nouveau aurait pu être défini dans une unité de traduction différente
Dans ce cas, les effets secondaires de new pourraient être tels qu’ils ne peuvent pas être optimisés. Mais si le compilateur peut garantir que le nouvel opérateur n'a pas d'effets secondaires, comme ce serait le cas si le code publié est le code complet, l'optimisation est valide.
Ce nouveau peut lancer std :: bad_alloc n'est pas une exigence. Dans ce cas, lorsque new est optimisé, le compilateur peut garantir qu'aucune exception ne sera levée ni aucun effet secondaire ne se produira.


L'histoire semble être que clang respecte les règles énoncées dans N3664: Clarifying Memory Allocation (Allocation de mémoire clarifiée) qui permet au compilateur d'optimiser les allocations de mémoire, mais comme le fait remarquer Nick Lewycky :

Shafik a souligné que cela semblait violer le lien de causalité, mais N3664 a commencé sa vie sous le nom de N3433, et je suis à peu près sûr que nous avons d'abord écrit l'optimisation, puis le papier par la suite.

Clang a donc implémenté l'optimisation qui est devenue par la suite une proposition qui a été implémentée dans le cadre de C ++ 14.

La question de base est de savoir s’il s’agit d’une optimisation valable antérieure à N3664 , c’est une question difficile. Nous devrions passer à la règle as-if couverte dans le projet de section 1.9 norme C ++ standard , Exécution du programme, qui stipule ( souligné par moi )

Les descriptions sémantiques de la présente Norme internationale définissent une machine abstraite non déterministe paramétrée. La présente Norme internationale n'impose aucune exigence à la structure des mises en œuvre conformes. En particulier, ils n'ont pas besoin de copier ou d'imiter la structure de la machine abstraite. Plutôt, des implémentations conformes sont requises pour émuler (uniquement) le comportement observable de la machine abstraite, comme expliqué ci-dessous. 5

où la note 5 dit:

Cette disposition est parfois appelée règle "as-if" , car une mise en oeuvre est libre de ne pas tenir compte de l'une quelconque des exigences de la présente Norme internationale, à condition que le résultat obtenu soit comme si l'exigence avait été respectée, dans la mesure où le comportement observable l'avait déterminé. du programme. Par exemple, une implémentation réelle n'a pas besoin d'évaluer une partie d'une expression si elle peut en déduire que sa valeur n'est pas utilisée et qu'aucun effet secondaire affectant le comportement observable du programme n'est produit.

Dans la mesure où new pourrait générer une exception qui aurait un comportement observable dans la mesure où elle modifierait la valeur de retour du programme, il semblerait que ce serait aller à l’encontre du fait qu’elle soit autorisée par la règle as-if .

Bien que l'on puisse faire valoir que le lancement d'une exception est un détail de la mise en œuvre et que, par conséquent, clang pourrait décider, même dans ce scénario, qu'il ne provoquerait pas d'exception et que, par conséquent, le new appel ne violerait pas la règle as-if .

Il semble également valable selon la règle as-if d'optimiser également l'appel à la version non lancée.

Mais nous pourrions avoir un nouvel opérateur mondial de remplacement dans une unité de traduction différente, ce qui pourrait avoir un effet sur le comportement observable. Le compilateur devrait donc pouvoir prouver que ce n'était pas le cas, sinon il ne pourrait pas effectuer cette optimisation. sans violer la règle as-if . Les versions précédentes de clang ont en effet été optimisées dans ce cas, comme le montre cet exemple de godbolt fourni par Casey en prenant le code suivant:

#include <cstddef>

extern void* operator new(std::size_t n);

template<typename T>
T* create() { return new T(); }

int main() {
    auto result = 0;
    for (auto i = 0; i < 1000000; ++i) {
        result += (create<int>() != nullptr);
    }

    return result;
}

et l'optimiser à ceci:

main:                                   # @main
    movl    $1000000, %eax          # imm = 0xF4240
    ret

Cela semble effectivement trop agressif, mais les versions ultérieures ne semblent pas le faire.


Le pire qui puisse arriver dans votre extrait est que new jette std::bad_alloc , ce qui n’est pas géré. Ce qui se passe alors est défini par l'implémentation.

Le meilleur des cas étant un no-op et le pire des cas, celui-ci n'étant pas défini, le compilateur est autorisé à les factoriser en non-existence. Maintenant, si vous essayez réellement d'attraper l'exception possible:

int main() try {
    int* mem = new int[100];
    return 0;
} catch(...) {
  return 1;
}

... alors l'appel à l' operator new est conservé .





language-lawyer