optimization variable - Quels types d'optimisations «volatile» empêchent-ils en C++?




arduino static (8)

En général, le compilateur suppose qu'un programme est à thread unique, il a donc une connaissance complète de ce qui se passe avec les valeurs variables. un compilateur intelligent peut alors prouver que le programme peut être transformé en un autre programme avec une sémantique équivalente mais avec de meilleures performances. par exemple

x = y+y+y+y+y;

peut être transformé en

x = y*5;

Cependant, si une variable peut être modifiée en dehors du thread, le compilateur n'a pas une connaissance complète de ce qui se passe en examinant simplement ce morceau de code. il ne peut plus faire d’optimisations comme ci-dessus. ( edit: ça peut probablement être le cas ici, nous avons besoin d'exemples plus sophistiqués )

par défaut, pour l'optimisation des performances, l'accès à un seul thread est supposé. cette hypothèse est généralement vraie. à moins que le programmeur n'indique explicitement le contraire avec le mot-clé volatile .

Je cherchais le mot-clé volatile et à quoi il servait, et la réponse que j'ai eue a été:

Il est utilisé pour empêcher le compilateur d'optimiser le code.

Il y avait quelques exemples, comme lors de l'interrogation de matériel mappé en mémoire: sans volatile la boucle d'interrogation serait supprimée car le compilateur pourrait reconnaître que la valeur de la condition n'est jamais modifiée. Mais comme il n'y avait qu'un seul exemple ou peut-être deux, cela m'a fait penser: Y a-t-il d'autres situations où nous devons utiliser des volatile pour éviter une optimisation indésirable? Les variables de condition sont-elles le seul endroit où la volatile est nécessaire?

J'imagine que l'optimisation est spécifique au compilateur et n'est donc pas spécifiée dans la spécification C ++. Est-ce que cela signifie que nous devons aller de l'avant, en disant Hm, je soupçonne que mon compilateur va supprimer cela si je ne déclare pas cette variable comme volatile ou y a-t-il des règles claires à suivre?


Rappelez-vous que la règle "comme si" signifie que le compilateur peut et doit faire ce qu'il veut, tant que le comportement vu de l'extérieur du programme dans son ensemble est le même. En particulier, alors qu'une variable nomme conceptuellement une zone en mémoire, il n'y a aucune raison pour qu'elle soit réellement en mémoire.

Il pourrait être dans un registre:

Sa valeur pourrait être calculée, par exemple dans:

int x = 2;
int y = x + 7;
return y + 1;

Vous n'avez pas besoin d'avoir un x et un y , mais vous pouvez simplement le remplacer par:

return 10;

Et un autre exemple, est que tout code qui n'affecte pas l'état de l'extérieur pourrait être entièrement supprimé. Par exemple, si vous modifiez les données sensibles, le compilateur peut voir cela comme un exercice inutile ("pourquoi écrivez-vous ce qui ne sera pas lu?") Et le supprimer. volatile peut être utilisé pour arrêter cela se produit.

volatile peut être considéré comme signifiant "l'état de cette variable doit être considéré comme faisant partie de l'état extérieurement visible, et ne pas être perturbé". Les optimisations qui l'utilisent autrement que littéralement après le code source ne sont pas autorisées.

(Une note C #. Beaucoup de choses que j’ai vues récemment sur les volatile suggèrent que les gens lisent C ++ volatile et l’appliquent à C #, et les lit en C # et l’appliquent à C ++). deux pour ne pas être utile de les considérer liés).


Une façon de penser à une variable volatile consiste à imaginer qu’elle est une propriété virtuelle; écrit et même lit peut faire des choses que le compilateur ne peut pas savoir. Le code réellement généré pour une écriture / lecture d'une variable volatile est simplement une écriture ou une lecture en mémoire (*), mais le compilateur doit considérer le code comme opaque; il ne peut faire aucune supposition sous laquelle il pourrait être superflu. Le problème n'est pas simplement de s'assurer que le code compilé remarque que quelque chose a provoqué la modification d'une variable. Sur certains systèmes, même les lectures de mémoire peuvent "faire" des choses.

(*) Sur certains compilateurs, des variables volatiles peuvent être ajoutées, soustraites, incrémentées, décrémentées, etc. en tant qu’opérations distinctes. Il est probablement utile pour un compilateur de compiler:

  volatilevar++;

comme

  inc [_volatilevar]

car cette dernière forme peut être atomique sur de nombreux microprocesseurs (mais pas sur les PC multi-cœurs modernes). Il est important de noter, cependant, que si la déclaration était:

  volatilevar2 = (volatilevar1++);

le code correct ne serait pas:

  mov ax,[_volatilevar1] ; Reads it once
  inc [_volatilevar]     ; Reads it again (oops)
  mov [_volatilevar2],ax

ni

  mov ax,[_volatilevar1]
  mov [_volatilevar2],ax ; Writes in wrong sequence
  inc ax
  mov [_volatilevar1],ax

mais plutôt

  mov ax,[_volatilevar1]
  mov bx,ax
  inc ax
  mov [_volatilevar1],ax
  mov [_volatilevar2],bx

Ecrire le code source différemment permettrait de générer du code plus efficace (et peut-être plus sûr). Si «volatilevar1» n’était pas dérangeant d’être lu deux fois et que «volatilevar2» n’était pas dérangeant d’être écrit avant volatilevar1, alors le fractionnement de l’instruction en

  volatilevar2 = volatilevar1;
  volatilevar1++;

permettrait un code plus rapide et peut-être plus sûr.


Le comportement observable d’un programme C ++ est déterminé par la lecture et l’écriture de variables volatiles, ainsi que par les appels aux fonctions d’entrée / sortie.

Cela implique que toutes les lectures et écritures sur des variables volatiles doivent avoir lieu dans l'ordre dans lequel elles apparaissent dans le code, et qu'elles doivent se produire. (Si un compilateur enfreignait l'une de ces règles, cela briserait la règle du as.)

C'est tout. Il est utilisé lorsque vous devez indiquer que lire ou écrire une variable doit être considéré comme un effet observable. (Notez que l'article "C ++ et les dangers du verrouillage à double vérification" aborde un peu cette question.)

Donc, pour répondre à la question du titre, cela empêche toute optimisation susceptible de réorganiser l'évaluation des variables volatiles par rapport à d'autres variables volatiles .

Cela signifie un compilateur qui change:

int x = 2;
volatile int y = 5;
x = 5;
y = 7;

À

int x = 5;
volatile int y = 5;
y = 7;

C'est bien, car la valeur de x ne fait pas partie du comportement observable (ce n'est pas volatil). Ce qui ne va pas, c’est de changer l’affectation de 5 à une assignation à 7, car cette écriture de 5 est un effet observable.


Les variables de condition ne sont pas volatile ; strictement, il n'est nécessaire que dans les pilotes de périphériques.

volatile garanties volatile que les lectures et écritures sur l'objet ne sont pas optimisées ou réorganisées par rapport à un autre volatile . Si vous êtes occupé en boucle sur une variable modifiée par un autre thread, il doit être déclaré volatile . Cependant, vous ne devriez pas occuper en boucle. Comme le langage n'était pas vraiment conçu pour le multithreading, ce n'est pas très bien supporté. Par exemple, le compilateur peut déplacer une écriture vers une variable non volatile de après à avant la boucle, en violation du verrou. (Pour les spinloops indéfinis, cela ne peut se produire que sous C ++ 0x.)

Lorsque vous appelez une fonction de bibliothèque de threads, elle agit comme une barrière de mémoire et le compilateur supposera que toutes les valeurs ont changé - essentiellement tout est volatile. Ceci est spécifié ou implicitement implémenté par n'importe quelle bibliothèque de threads pour que les roues restent fluides.

C ++ 0x peut ne pas avoir ce défaut, car il introduit la sémantique formelle du multithreading. Je ne suis pas vraiment familier avec les changements, mais pour des raisons de compatibilité ascendante, il n'est pas nécessaire de déclarer quelque chose de volatile qui ne l'était pas auparavant.


Volatile n'essaie pas de garder les données dans un registre CPU (100 fois plus rapide que la mémoire). Il doit le lire en mémoire chaque fois qu'il est utilisé.


Fondamentalement, volatile annonce qu'une valeur peut changer derrière le retour de votre programme. Cela empêche les compilateurs de mettre en cache la valeur (dans un registre de CPU) et d'optimiser les accès à cette valeur lorsqu'ils semblent inutiles dans le POV de votre programme.

Ce qui devrait déclencher l'utilisation de volatile est lorsqu'une valeur change malgré le fait que votre programme ne lui a pas écrit, et quand aucune autre barrière de mémoire (comme des mutex tels qu'utilisés pour les programmes multithread) n'est présente.


Je voulais juste ajouter un peu d'info, car je ne l'ai pas encore vu.

Vous verrez très souvent du code dans les en-têtes C comme ceci:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Ce que cela permet, c'est qu'il vous permet d'utiliser ce fichier d'en-tête C avec votre code C ++, car la macro "__cplusplus" sera définie. Mais vous pouvez toujours l'utiliser avec votre code C hérité, où la macro n'est pas définie, donc il ne verra pas la construction C ++ unique.

Bien que, j'ai également vu le code C ++ tel que:

extern "C" {
#include "legacy_C_header.h"
}

ce que j'imagine accomplit à peu près la même chose.

Je ne sais pas quel est le meilleur, mais j'ai vu les deux.





c++ optimization volatile