28 votes

Les lectures volatiles mais non clôturées peuvent-elles produire des valeurs indéfiniment périmées? (sur du vrai matériel)

En répondant à cette question, une autre question au sujet de l'OP situation que je n'étais pas sûr au sujet: c'est surtout une architecture de processeur question, mais avec un effet d'entraînement sur la question sur le C++ 11 mémoire de ce modèle.

Fondamentalement, les OP du code était de boucler à l'infini à plus l'optimisation des niveaux de raison de le code suivant (légèrement modifié pour des raisons de simplicité):

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

__sync_val_compare_and_swap() est GCC atomique de CAS intégré. GCC (raisonnablement) optimisé ce dans une boucle infinie dans le cas d' bits_ & mask a été détecté à être true avant d'entrer dans la boucle, en sautant, en CAS d'opération entièrement, j'ai donc proposé la modification suivante (qui fonctionne):

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        __sync_synchronize();
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

Après j'ai répondu, OP noter que le changement de bits_ de volatile uint8_t semble fonctionner aussi bien. Je suggère de ne pas aller dans cette voie, depuis volatile ne devrait normalement pas être utilisé pour la synchronisation, et il ne semble pas être beaucoup inconvénient de l'utilisation d'une clôture ici de toute façon.

Cependant, j'y pensais plus, et dans ce cas, la sémantique est telle qu'il n'a pas vraiment d'importance si l' ov & MASK contrôle est basé sur un état de la valeur, tant qu'il ne repose pas sur une indéfiniment rassis un (c'est à dire tant que la boucle est cassé par la suite), car la réelle tentative de mise à jour bits_ est synchronisé. Donc, est - volatile assez de place pour garantir que cette boucle s'arrête finalement si bits_ est mise à jour par un autre thread, tels que bits_ & MASK == false, pour toute inexistants processeur? En d'autres termes, en l'absence d'une mémoire explicite de la clôture, est-il possible pour ne lit pas optimisé par le compilateur pour être optimisé par le processeur au lieu de cela, indéfiniment? (EDIT: Pour être clair, je pose la question ici à propos de ce matériel moderne pourrait effectivement le faire compte tenu de l'hypothèse que les lectures sont émis dans une boucle par le compilateur, il n'est donc pas techniquement une langue en question, bien que l'exprimant en termes de C++ sémantique est commode.)

C'est le matériel de l'angle, mais de le mettre à jour légèrement et de le rendre aussi un responsable de la question sur le C++11 modèle de mémoire ainsi, considérer la variation suivante pour le code ci-dessus:

// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
    if (ov & MASK) {
        ov = bits_.load(std::memory_order_relaxed);
        continue;
    }
    // compare_exchange_weak also updates ov if the exchange fails
    if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
        break;
    }
}

cppreference prétend qu' std::memory_order_relaxed implique "pas de contraintes sur la réorganisation de l'accès à la mémoire autour de la variable atomique", de manière indépendante de ce matériel sera ou ne sera pas, implique que bits_.load(std::memory_order_relaxed) pourrait techniquement jamais lire une valeur mise à jour après l' bits_ est mis à jour sur un autre thread dans une mise en œuvre conforme?

EDIT: j'ai trouvé ça dans la norme (de 29,4 p13):

Implémentations devraient faire atomique magasins visible atomique des charges à l'intérieur d'un laps de temps raisonnable.

Donc, apparemment en attente "infiniment long" pour une mise à jour de la valeur est (surtout?) hors de question, mais il n'y a aucune garantie de quelque intervalle de temps spécifique de fraîcheur, d'autres que c'est doit être "raisonnable"; encore, la question sur le matériel réel le comportement des peuplements.

9voto

Pete Becker Points 27371

C++11 atomics traiter trois questions:

  1. s'assurer qu'une valeur est lue ou écrite sans fil commutateur; cela empêche la déchirure.

  2. veiller à ce que le compilateur de ne pas re-instructions de commande à l'intérieur d'un fil en travers de la atomique en lecture ou en écriture, ce qui assure la commande dans le thread.

  3. assurer (pour les choix de la mémoire paramètres de commande) que les données écrites dans un thread avant atomique écrire sera vu par un thread qui lit la variable atomique et voit la valeur qui a été écrit. C'est la visibilité.

Lorsque vous utilisez memory_order_relaxed vous n'obtenez pas une garantie de visibilité de l'atmosphère détendue de la stocker ou de la charge. Vous obtenez les deux premières garanties.

Les implémentations "devrait" (c'est à dire sont encouragés à) faire de la mémoire, écrit visible à l'intérieur d'une quantité raisonnable de temps, même avec détendue de la commande. C'est le mieux que l'on puisse dire; tôt ou tard, ces choses devrait s'afficher.

Donc, oui, officiellement, une mise en œuvre qui n'a jamais fait détendu écrit visible détendu lit est conforme à la définition du langage. Dans la pratique, cela ne se produira pas.

Quant à ce qui volatile t, demandez à votre fournisseur de compilateur. C'est à la mise en œuvre.

4voto

Patashu Points 14053

Il est techniquement légal pour std::memory_order_relaxed charges à jamais, ne jamais revenir une nouvelle valeur pour la charge. Quant à savoir si toute mise en œuvre ferai ceci, je n'ai aucune idée.

Référence: http://www.developerfusion.com/article/138018/memory-ordering-for-atomic-operations-in-c0x/ "La seule exigence est que accède à une seule variable atomique à partir de la même thread ne peut pas être réorganisées: une fois qu'un thread a vu un particulier de la valeur d'une variable atomique, une lecture ultérieure par ce thread ne peut pas récupérer une version antérieure de la valeur de la variable."

4voto

Maciej Piechotka Points 3422

Si les processeurs n'ont pas de cache-cohérence protocole ou il est très simple alors il peut "optimiser" les charges de l'extraction de données obsolètes dans le cache. Maintenant, le plus moderne, le multi-core de mettre en œuvre protocole de cohérence de cache. Cependant le BRAS avant de l'A9 ne l'avait pas. Non-architectures des processeurs aussi peut ne pas avoir de cache-cohérence (bien qu'il serait sans doute de ne pas adhérer à C++ modèle de mémoire).

Un autre problème est que beaucoup d'architecture (y compris l'ARM et x86) permettre la réorganisation des accès à la mémoire. Je ne sais pas si les processeurs sont assez intelligents pour avis les accès répétés à la même adresse, mais j'en doute (il en coûte de temps et d'espace de rares cas, comme le compilateur doit être en mesure de le remarquer, avec de petits avantages, comme plus tard les accès seront probablement L1 hits), mais, techniquement, on peut spéculer que la branche va être pris et il peut réorganiser deuxième accès en avant première (peu probable, mais si je lis Intel et ARM manuel correctement, c'est permis).

Enfin, il existe des appareils externes qui n'adhèrent pas à cache-cohérence. Si le PROCESSEUR communique par mappés en mémoire IO/DMA puis la page doit être marquée comme non cachable " (autrement en L1/L2/L3/ cache... serait staled de données). Dans de tels cas, le processeur ne sera pas généralement de réorganiser de lecture et d'écriture (pour plus de détails, consultez votre processeur manuel - c'contrôle plus fin) - compilateur peut donc vous devez utiliser volatile. Cependant, comme atomics sont généralement mis en cache, vous n'avez pas besoin ou de les utiliser.

Je crains que je ne peux pas répondre si une telle forte cohérence de cache sera disponible dans les futurs processeurs. Je dirais en suivant strictement le cahier des charges ("Quel est le problème dans le stockage de pointeur sur int? Sûrement personne ne sera jamais à l'utilisateur plus de 4GiB donc 32b adresse est assez grand."). L'exactitude a été répondu par d'autres, donc je ne vais pas l'inclure.

1voto

didierc Points 8128

Voici mon prendre sur elle, si je n'ai pas beaucoup de connaissance sur le sujet, afin de prendre cela avec un grain de sel.

L' volatile mot-clé de l'effet pourrait bien être dépendant du compilateur, mais je suppose qu'il fasse ce qu'intuitivement on attend de lui, à savoir éviter l'aliasing ou de tout autre optimisation qui ne serait pas permettre à un utilisateur d'inspecter la valeur de la variable dans un débogueur à n'importe quel point de l'exécution au cours de la vie de cette variable. C'est très proche (et probablement le même que) que de réponses sur le sens de la volatilité.

L'implication directe est que le code bloquer l'accès à l' volatile variable v devront s'engager à la mémoire dès qu'il a modifié. Clôtures fera en sorte que cela se passe dans l'ordre avec d'autres mises à jour, mais de toute façon, il y aura un magasin en v dans l'assemblage de sortie si v est modifiée au niveau de la source.

En effet, la question qui se pose est, si v, chargées dans un registre, n'a pas été modifié par un calcul, ce qui force le PROCESSEUR à exécuter une lecture à partir d' v de nouveau à un registre, par opposition à la simple réutilisation de la valeur qu'il a déjà obtenu plus tôt.

Je pense que la réponse est que le CPU ne peut pas supposer qu'une cellule de mémoire n'a pas changé depuis sa dernière lecture. L'accès à la mémoire, même sur un seul système de base, n'est pas strictement réservés à la CPU. De nombreux autres sous-systèmes peuvent accéder en lecture-écriture (c'est le principe derrière DMA).

Le plus sûr, l'optimisation de la un CPU peut probablement faire est de vérifier si la valeur a changé dans le cache ou pas, et de l'utiliser comme un indicateur de l'état de l' v dans la mémoire. Les Caches doivent être synchronisées. avec de la mémoire grâce à l'invalidation du cache de mécanismes attachée avec de la DMA. Avec cette condition, le problème revient à la cohérence du cache sur multicœur, et "write after write" pour le multithreading situations. Ce dernier problème ne peut pas être traitée efficacement avec simple volatile variables, depuis leur opération n'est pas atomique, comme vous le savez déjà.

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