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



volatile variable c (6)

À moins que vous ne soyez sur un système embarqué ou que vous écrivez des pilotes de matériel où le mappage de mémoire est utilisé comme moyen de communication, vous ne devriez jamais utiliser de

Considérer:

int main()
{
    volatile int SomeHardwareMemory; //This is a platform specific INT location. 
    for(int idx=0; idx < 56; ++idx)
    {
        printf("%d", SomeHardwareMemory);
    }
}

Doit produire du code comme:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loopTop:
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

alors que sans volatile cela pourrait être:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
loopTop:
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

L'hypothèse de volatile est que l'emplacement mémoire de la variable peut être modifié. Vous forcez le compilateur à charger la valeur réelle de la mémoire chaque fois que la variable est utilisée; et vous indiquez au compilateur que la réutilisation de cette valeur dans un registre n'est pas autorisée.

https://code.i-harness.com

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?


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 .


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.


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.


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é.





volatile