web-dev-qa-db-fra.com

Quel type d'optimisation offre const en C / C ++?

Je sais que dans la mesure du possible, vous devez utiliser le mot clé const lorsque vous passez des paramètres par référence ou par pointeur pour des raisons de lisibilité. Y a-t-il des optimisations que le compilateur peut faire si je spécifie qu'un argument est constant?

Il pourrait y avoir quelques cas:

Paramètres de fonction:

Référence constante:

void foo(const SomeClass& obj)

Objet SomeClass constant:

void foo(const SomeClass* pObj)

Et pointeur constant vers SomeClass:

void foo(SomeClass* const pObj)

Déclarations variables:

const int i = 1234

Déclarations de fonction:

const char* foo()

Quel type d'optimisation du compilateur chacun propose (le cas échéant)?

56
UnTraDe

[Les lecteurs notent que la majorité de ce message est tirée d'un article de Herb Sutter - http://www.gotw.ca/gotw/081.htm - sans attribution par le PO.]

CAS_1: -

Lorsque vous déclarez un const dans votre programme,

int const x = 2;

Le compilateur peut optimiser cette const en ne fournissant pas de stockage à cette variable plutôt l'ajouter dans la table des symboles. Ainsi, la lecture suivante a juste besoin d'une indirection dans la table des symboles plutôt que d'instructions pour récupérer la valeur de la mémoire.

REMARQUE: - Si vous faites quelque chose comme ci-dessous: -

const int x = 1;
const int* y = &x;

Cela forcerait alors le compilateur à allouer de l'espace pour 'x'. Donc, ce degré d'optimisation n'est pas possible dans ce cas.

En termes de paramètres de fonction const signifie que le paramètre n'est pas modifié dans la fonction. Pour autant que je sache, il n'y a pas de gain de performances substantiel pour l'utilisation de const c'est plutôt un moyen de garantir l'exactitude.

CAS_2: -

"La déclaration du paramètre et/ou de la valeur de retour en tant que const aide-t-elle le compilateur à générer un code plus optimal?"

  const Y& f( const X& x )
  {
    // ... do something with x and find a Y object ...
    return someY;
  }

Ques => Qu'est-ce que le compilateur pourrait faire de mieux?

=> Pourrait-il éviter une copie du paramètre ou de la valeur de retour?

Non, car l'argument est déjà transmis par référence.

=> Pourrait-il mettre une copie de x ou someY dans la mémoire morte?

Non, car x et certains Y vivent en dehors de son champ d'application et proviennent et/ou sont donnés au monde extérieur. Même si someY est alloué dynamiquement à la volée dans f() lui-même, lui et sa propriété sont cédés à l'appelant.

Ques => Qu'en est-il des optimisations possibles du code qui apparaît à l'intérieur du corps de f ()? En raison de la const, le compilateur pourrait-il améliorer le code qu'il génère pour le corps de f ()?

Même lorsque vous appelez une fonction membre const, le compilateur ne peut pas supposer que les bits de l'objet x ou de l'objet someY ne seront pas modifiés. En outre, il existe des problèmes supplémentaires (à moins que le compilateur n'effectue une optimisation globale): le compilateur peut également ne pas savoir avec certitude qu'aucun autre code ne peut avoir une référence non const qui aliase le même objet que x et/ou someY, et si une telle les références non const au même objet peuvent être utilisées accidentellement lors de l'exécution de f (); et le compilateur peut même ne pas savoir si les objets réels, auxquels x et someY ne sont que des références, ont été effectivement déclarés const en premier lieu.

CAS_3: -

  void f( const Z z )
  {
    // ...
  }

Ques => Y aura-t-il une optimisation à ce sujet?

Oui, car le compilateur sait que z est vraiment un objet const, il pourrait effectuer des optimisations utiles même sans analyse globale. Par exemple, si le corps de f() contient un appel comme g (& z), le compilateur peut être sûr que les parties non mutables de z ne changent pas pendant l'appel à g ( )

28
ravi

Avant de donner une réponse, je tiens à souligner que la raison d'utiliser ou de ne pas utiliser const devrait vraiment être pour la correction du programme et pour la clarté pour les autres développeurs plus que pour les optimisations du compilateur; c'est-à-dire, la création d'un paramètre const indique que la méthode ne modifiera pas ce paramètre et la création d'une fonction membre const indique que ce membre ne modifiera pas l'objet dont il est membre (à du moins pas d'une manière qui modifie logiquement la sortie de toute autre fonction membre const). Cela permet, par exemple, aux développeurs d'éviter de faire des copies inutiles d'objets (car ils n'ont pas à craindre que l'original soit détruit ou modifié) ou d'éviter une synchronisation de thread inutile (par exemple en sachant que tous les threads se contentent de lire et de faire pas muter l’objet en question).

En termes d'optimisations, un compilateur pourrait faire, au moins en théorie, bien que dans un mode d'optimisation qui lui permette de faire certaines hypothèses non standard qui pourraient casser le code C++ standard, considérez:

for (int i = 0; i < obj.length(); ++i) {
   f(obj);
}

Supposons que la fonction length est marquée comme const mais est en fait une opération coûteuse (disons qu'elle fonctionne réellement en O(n) time au lieu de O(1) time). Si la fonction f prend son paramètre par const référence, alors le compilateur pourrait potentiellement optimiser cette boucle pour:

int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
   f(obj);
}

... car le fait que la fonction f ne modifie pas le paramètre garantit que la fonction length doit renvoyer les mêmes valeurs à chaque fois étant donné que l'objet n'a pas changé. Cependant, si f est déclaré prendre le paramètre par une référence mutable, alors length devra être recalculé à chaque itération de la boucle, car f aurait pu modifier le paramètre objet de manière à produire une modification de la valeur.

Comme indiqué dans les commentaires, cela suppose un certain nombre de mises en garde supplémentaires et ne serait possible que lors de l'appel du compilateur dans un mode non standard qui lui permet de faire des hypothèses supplémentaires (telles que les méthodes const sont strictement une fonction de leurs entrées et que les optimisations peuvent supposer que le code n'utilisera jamais const_cast pour convertir un paramètre de référence const en une référence mutable).

12

Paramètres de fonction:

const n'est pas significatif pour la mémoire référencée. C'est comme attacher une main derrière le dos de l'optimiseur.

Supposons que vous appeliez une autre fonction (par exemple void bar()) dans foo qui n'a pas de définition visible. L'optimiseur aura une restriction car il n'a aucun moyen de savoir si bar a modifié le paramètre de fonction passé à foo (par exemple via l'accès à la mémoire globale). La possibilité de modifier la mémoire en externe et l'alias introduisent des restrictions importantes pour les optimiseurs dans ce domaine.

Bien que vous ne l'ayez pas demandé, const les valeurs pour les paramètres de fonction permettent des optimisations car l'optimiseur est garanti un objet const . Bien sûr, le coût de copie de ce paramètre peut être beaucoup plus élevé que les avantages de l'optimiseur.

Voir: http://www.gotw.ca/gotw/081.htm


Déclarations de variables: const int i = 1234

Cela dépend de l'endroit où il est déclaré, de sa création et du type. Cette catégorie est en grande partie là où les optimisations const existent. Il n'est pas défini de modifier un objet const ou une constante connue, de sorte que le compilateur est autorisé à effectuer certaines optimisations; il suppose que vous n'invoquez pas un comportement indéfini et cela introduit certaines garanties.

const int A(10);
foo(A);
// compiler can assume A's not been modified by foo

De toute évidence, un optimiseur peut également identifier les variables qui ne changent pas:

for (int i(0), n(10); i < n; ++i) { // << n is not const
 std::cout << i << ' ';
}

Déclarations de fonction: const char* foo()

Insignifiant. La mémoire référencée peut être modifiée en externe. Si la variable référencée renvoyée par foo est visible, un optimiseur pourrait effectuer une optimisation, mais cela n'a rien à voir avec la présence/absence de const sur le type de retour de la fonction.

Encore une fois, une valeur ou un objet const est différent:

extern const char foo[];

6
justin

Les effets exacts de const diffèrent pour chaque contexte où il est utilisé. Si const est utilisé lors de la déclaration d'une variable, elle est physiquement const et réside puissamment dans la mémoire morte.

const int x = 123;

Essayer de rejeter la constance est un comportement indéfini:

Même si const_cast peut supprimer la constance ou la volatilité de tout pointeur ou référence, l'utilisation du pointeur ou de la référence résultante pour écrire dans un objet déclaré const ou pour accéder à un objet déclaré volatile appelle un comportement indéfini. cppreference/const_cast

Dans ce cas, le compilateur peut donc supposer que la valeur de x est toujours 123. Cela ouvre un certain potentiel d'optimisation (propagation des constantes)

Pour les fonctions, c'est une autre affaire. Supposer:

void doFancyStuff(const MyObject& o);

notre fonction doFancyStuff peut faire l'une des choses suivantes avec o.

  1. pas modifier l'objet.
  2. rejeter la constance, puis modifier l'objet
  3. modifier un mutable membre de données de MyObject

Notez que si vous appelez notre fonction avec une instance de MyObject qui a été déclarée comme const, vous invoquerez un comportement indéfini avec # 2.

Question du gourou: les éléments suivants invoqueront-ils un comportement non défini?

const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();
3
Stefan

SomeClass* const pObj crée un objet constant de type pointeur. Il n'existe aucune méthode sûre pour changer un tel objet, donc le compilateur peut, par exemple, le mettre en cache dans un registre avec une seule lecture en mémoire, même si son adresse est prise.

Les autres n'activent aucune optimisation spécifiquement, bien que le qualificatif const du type affectera la résolution de surcharge et entraînera éventuellement la sélection de fonctions différentes et plus rapides.

3
Ben Voigt