95 votes

Essayer de comprendre l'option gcc -fomit-frame-pointer

J'ai demandé à Google de me donner la signification de l'expression gcc option -fomit-frame-pointer qui me redirige vers la déclaration ci-dessous.

-fomit-frame-pointer

Ne gardez pas le pointeur de cadre dans un registre pour les fonctions qui n'en ont pas besoin. Cela évite les instructions pour sauvegarder, configurer et restaurer les pointeurs de trame ; cela rend également un registre supplémentaire disponible dans de nombreuses fonctions. Cela rend également le débogage impossible sur certaines machines.

Selon mes connaissances de chaque fonction, un enregistrement d'activation sera créé dans la pile de la mémoire du processus pour garder toutes les variables locales et quelques autres informations. J'espère que ce pointeur de cadre signifie l'adresse de l'enregistrement d'activation d'une fonction.

Dans ce cas, quels sont les types de fonctions pour lesquelles il n'est pas nécessaire de conserver le pointeur de cadre dans un registre ? Si j'obtiens cette information, j'essaierai de concevoir la nouvelle fonction sur cette base (si possible), car si le pointeur de trame n'est pas conservé dans les registres, certaines instructions seront omises en binaire. Cela améliorera vraiment les performances de façon notable dans une application où il y a beaucoup de fonctions.

74voto

Mats Petersson Points 70074

La plupart des petites fonctions n'ont pas besoin d'un pointeur de cadre - les grandes fonctions PEUVENT en avoir besoin.

Il s'agit en fait de savoir si le compilateur parvient à suivre la façon dont la pile est utilisée, et où se trouvent les choses sur la pile (variables locales, arguments passés à la fonction courante et arguments préparés pour une fonction sur le point d'être appelée). Je ne pense pas qu'il soit facile de caractériser les fonctions qui ont besoin ou non d'un frame pointer (techniquement, AUCUNE fonction NE DOIT avoir un frame pointer - c'est plus un cas de "si le compilateur le juge nécessaire pour réduire la complexité d'autres codes").

Je ne pense pas que vous devriez "essayer de faire en sorte que les fonctions n'aient pas de pointeur de cadre" dans le cadre de votre stratégie de codage - comme je l'ai dit, les fonctions simples n'en ont pas besoin, donc utilisez -fomit-frame-pointer Vous obtiendrez un registre de plus pour l'allocateur de registre et vous économiserez 1 à 3 instructions sur l'entrée/sortie des fonctions. Si votre fonction nécessite un frame pointer, c'est parce que le compilateur décide que c'est une meilleure option que de ne pas utiliser de frame pointer. L'objectif n'est pas d'avoir des fonctions sans frame pointer, mais d'avoir un code qui fonctionne à la fois correctement et rapidement.

Notez que le fait de "ne pas avoir de pointeur de trame" devrait améliorer les performances, mais ce n'est pas une solution miracle qui apporte d'énormes améliorations - en particulier sur x86-64, qui a déjà 16 registres pour commencer. Sur x86 32 bits, puisqu'il n'y a que 8 registres, l'un d'entre eux est le pointeur de pile, et en prendre un autre comme pointeur de cadre signifie que 25% de l'espace registre est pris. Changer cela en 12,5% est une amélioration considérable. Bien sûr, la compilation pour 64 bits sera également d'une grande aide.

27voto

Maxim Masiutin Points 1454

Il s'agit du registre BP/EBP/RBP sur les plateformes Intel. Ce registre est par défaut dans le segment de pile (il n'a pas besoin d'un préfixe spécial pour accéder au segment de pile).

L'EBP est le meilleur choix de registre pour accéder aux structures de données, aux variables et à l'espace de travail alloué dynamiquement dans la pile. L'EBP est souvent utilisé pour accéder à des éléments de la pile par rapport à un point fixe de la pile plutôt que par rapport au TOS actuel. Il identifie généralement l'adresse de base du cadre de pile actuel établi pour la procédure en cours. Lorsque EBP est utilisé comme registre de base dans un calcul de décalage, le décalage est calculé automatiquement dans le segment de pile actuel (c'est-à-dire le segment actuellement sélectionné par SS). Comme il n'est pas nécessaire de spécifier explicitement SS, le codage des instructions est plus efficace dans ce cas. EBP peut également être utilisé pour indexer dans des segments adressables par d'autres registres de segment.

( source - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )

Comme sur la plupart des plates-formes 32 bits, le segment de données et le segment de pile sont identiques, cette association de EBP/RBP avec la pile n'est plus un problème. Il en va de même sur les plates-formes 64 bits : L'architecture x86-64, introduite par AMD en 2003, a largement abandonné le support de la segmentation en mode 64 bits : quatre des registres de segment : CS, SS, DS et ES sont forcés à 0. Ces circonstances des plateformes x86 32 bits et 64 bits signifient essentiellement que le registre EBP/RBP peut être utilisé, sans aucun préfixe, dans les instructions du processeur qui accèdent à la mémoire.

L'option du compilateur dont vous parlez permet donc d'utiliser le BP/EBP/RBP à d'autres fins, par exemple pour contenir une variable locale.

Par "Cela permet d'éviter les instructions de sauvegarde, de mise en place et de restauration des frame pointers", on entend éviter le code suivant à l'entrée de chaque fonction :

push ebp
mov ebp, esp

ou le enter qui était très utile sur les processeurs Intel 80286 et 80386.

De même, avant le retour de la fonction, le code suivant est utilisé :

mov esp, ebp
pop ebp 

ou le leave l'instruction.

Les outils de débogage peuvent analyser les données de la pile et utiliser ces données de registre EBP poussé pour localiser call sites c'est-à-dire d'afficher les noms de la fonction et des arguments dans l'ordre où ils ont été appelés hiérarchiquement.

Les programmeurs peuvent avoir des questions sur les cadres de pile non pas au sens large (qu'il s'agit d'une entité unique dans la pile qui sert à un seul appel de fonction et conserve l'adresse de retour, les arguments et les variables locales) mais au sens étroit - quand le terme stack frames est mentionné dans le contexte des options du compilateur. Du point de vue du compilateur, une trame de pile est juste le code d'entrée et de sortie de la routine Les outils de débogage peuvent analyser les données de la pile et utiliser ces points d'ancrage pour le retour en arrière, tout en localisant les points d'ancrage. Les outils de débogage peuvent scanner les données de la pile et utiliser ces ancres pour le retour en arrière, tout en localisant call sites dans la pile, c'est-à-dire pour afficher les noms des fonctions dans le même ordre qu'elles ont été appelées hiérarchiquement.

C'est pourquoi il est vital pour un programmeur de comprendre ce qu'est un stack frame en termes d'options du compilateur - car le compilateur peut contrôler s'il faut générer ce code ou non.

Dans certains cas, le cadre de pile (code d'entrée et de sortie de la routine) peut être omis par le compilateur, et les variables seront directement accessibles via le pointeur de pile (SP/ESP/RSP) plutôt que le pointeur de base pratique (BP/ESP/RSP). Les conditions pour qu'un compilateur omette les cadres de pile pour certaines fonctions peuvent être différentes, par exemple : (1) la fonction est une fonction feuille (c'est-à-dire une entité finale qui n'appelle pas d'autres fonctions) ; (2) aucune exception n'est utilisée ; (3) aucune routine n'est appelée avec des paramètres sortants sur la pile ; (4) la fonction n'a pas de paramètres.

L'omission des stack frames (code d'entrée et de sortie de la routine) peut rendre le code plus petit et plus rapide. Cependant, elles peuvent également affecter négativement la capacité des débogueurs à retracer les données de la pile et à les afficher au programmeur. Ce sont les options du compilateur qui déterminent sous quelles conditions une fonction doit satisfaire pour que le compilateur lui attribue le code d'entrée et de sortie du cadre de pile. Par exemple, un compilateur peut disposer d'options permettant d'ajouter ces codes d'entrée et de sortie aux fonctions dans les cas suivants : (a) toujours, (b) jamais, (c) lorsque cela est nécessaire (en précisant les conditions).

Revenir des généralités aux particularités : si vous utilisez l'option -fomit-frame-pointer Option du compilateur GCC, vous pouvez gagner sur le code d'entrée et de sortie de la routine, et sur le fait d'avoir un registre supplémentaire (à moins qu'il ne soit déjà activé par défaut soit lui-même soit implicitement par d'autres options, dans ce cas, vous bénéficiez déjà du gain de l'utilisation du registre EBP/RBP et aucun gain supplémentaire ne sera obtenu en spécifiant explicitement cette option si elle est déjà activée implicitement). Notez cependant que dans les modes 16 bits et 32 bits, le registre BP n'a pas la possibilité de donner accès à ses parties 8 bits comme le fait AX (AL et AH).

Cette option, en plus de permettre au compilateur d'utiliser EBP comme un registre à usage général dans les optimisations, empêche également de générer du code de sortie et d'entrée pour le cadre de pile, ce qui complique le débogage - c'est pourquoi l'option Documentation GCC indique explicitement (en soulignant inhabituellement avec un style gras) que l'activation de cette option rend le débogage impossible sur certaines machines .

Sachez également que d'autres options du compilateur, liées au débogage ou à l'optimisation, peuvent implicitement désactiver l'option -fomit-frame-pointer l'option ON ou OFF.

Je n'ai pas trouvé d'information officielle à gcc.gnu.org sur la façon dont les autres options affectent le système. -fomit-frame-pointer sur les plates-formes x86 , le site https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html ne stipule que ce qui suit :

-O active également -fomit-frame-pointer sur les machines où cela n'interfère pas avec le débogage.

Il n'est donc pas clair de la documentation en tant que telle si -fomit-frame-pointer sera activé si vous compilez avec une seule option `-O' sur une plateforme x86. Cela peut être testé empiriquement, mais dans ce cas il n'y a aucun engagement de la part des développeurs de GCC de ne pas changer le comportement de cette option dans le futur sans préavis.

Cependant, Peter Cordes a fait remarquer dans les commentaires qu'il y a une différence pour les paramètres par défaut de la -fomit-frame-pointer entre les plateformes x86-16 et les plateformes x86-32/64.

Cette option - -fomit-frame-pointer - est également concernant le compilateur Intel C++ 15.0 et pas seulement au CCG :

Pour le compilateur Intel, cette option a un alias /Oy .

Voici ce qu'Intel a écrit à ce sujet :

Ces options déterminent si EBP est utilisé comme un registre à usage général dans les optimisations. Les options -fomit-frame-pointer et /Oy permettent cette utilisation. Les options -fno-omit-frame-pointer et /Oy- l'interdisent.

Certains débogueurs s'attendent à ce que EBP soit utilisé comme un pointeur de cadre de pile, et ne peuvent produire un backtrace de la pile que si c'est le cas. Les options -fno-omit-frame-pointer et /Oy- demandent au compilateur de générer du code qui maintient et utilise EBP comme un pointeur de cadre de pile pour toutes les fonctions, de sorte qu'un débogueur puisse toujours produire un retour arrière de la pile sans faire ce qui suit :

Pour -fno-omit-frame-pointer : désactiver les optimisations avec -O0 Pour /Oy- : désactiver les optimisations avec /O1, /O2, ou /O3 L'option -fno-omit-frame-pointer est définie lorsque vous spécifiez l'option -O0 ou l'option -g. L'option -fomit-frame-pointer est définie lorsque vous spécifiez l'option -O1, -O2, ou -O3.

L'option /Oy est définie lorsque vous spécifiez l'option /O1, /O2 ou /O3. L'option /Oy- est définie lorsque vous spécifiez l'option /Od.

L'utilisation de l'option -fno-omit-frame-pointer ou /Oy- réduit le nombre de registres polyvalents disponibles de 1 et peut entraîner un code légèrement moins efficace.

NOTE Pour les systèmes Linux* : Il y a actuellement un problème avec la gestion des exceptions de GCC 3.2. Par conséquent, le compilateur Intel ignore cette option lorsque GCC 3.2 est installé pour C++ et que la gestion des exceptions est activée (par défaut).

Veuillez noter que la citation ci-dessus ne s'applique qu'au compilateur Intel C++ 15, et non à GCC.

4voto

plugwash Points 795

Je n'ai jamais rencontré le terme "enregistrement d'activation" auparavant, mais je suppose qu'il se réfère à ce que l'on appelle normalement un "cadre de pile". C'est la zone de la pile utilisée par la fonction courante.

Le pointeur de trame est un registre qui contient l'adresse de la trame de la pile de la fonction courante. Si un pointeur de cadre est utilisé, à l'entrée de la fonction, l'ancien pointeur de cadre est sauvegardé sur la pile et le pointeur de cadre est défini comme le pointeur de pile. En quittant la fonction, l'ancien pointeur de cadre est restauré.

La plupart des fonctions normales n'ont pas besoin d'un pointeur de trame pour leur propre fonctionnement. Le compilateur peut garder la trace du décalage du pointeur de pile sur tous les chemins de code à travers la fonction et générer des accès aux variables locales en conséquence.

Un pointeur de trame peut être important dans certains contextes pour le débogage et la gestion des exceptions. Cela devient cependant de plus en plus rare car les formats modernes de débogage et de gestion des exceptions sont conçus pour supporter des fonctions sans pointeur de trame dans la plupart des cas.

Le principal cas où un pointeur de cadre est nécessaire aujourd'hui est celui où une fonction utilise des tableaux de longueur variable ou des tableaux d'allocation. Dans ce cas, la valeur du pointeur de pile ne peut pas être suivie statiquement.

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