Pourquoi std :: atomic <T> :: is_lock_free () n'est-il pas aussi statique que constexpr?
9
Quelqu'un peut-il me dire si std :: atomic :: is_lock_free () n'est pas statique aussi bien que constexpr? L'avoir non statique et / ou non constexpr n'a pas de sens pour moi.
@MaxLanghof Voulez-vous dire que toutes les instances ne seront pas alignées de la même manière?
curiousguy
1
Mike, non, je n'étais pas au courant, mais merci pour cet indice; Il est vraiment utile pour moi. Mais je me demande pourquoi il y a une décision entre is_lock_free () et is_always_lock_free. Cela ne peut pas être dû à l'atomique non aligné, comme d'autres l'ont suggéré ici, car le langage définit les accès non alignés pour avoir un comportement non défini de toute façon.
Tous les types atomiques à l'exception de std :: atomic_flag peuvent être implémentés en utilisant des mutex ou d'autres opérations de verrouillage, plutôt qu'en utilisant les instructions CPU atomiques sans verrouillage. Les types atomiques peuvent également être parfois sans verrouillage, par exemple si seuls les accès mémoire alignés sont naturellement atomiques sur une architecture donnée, les objets mal alignés du même type doivent utiliser des verrous.
La norme C ++ recommande (mais n'exige pas) que les opérations atomiques sans verrouillage soient également sans adresse, c'est-à-dire adaptées à la communication entre les processus utilisant la mémoire partagée.
Comme mentionné par plusieurs autres, c'est std::is_always_lock_freepeut-être ce que vous recherchez vraiment.
Edit: Pour clarifier, les types d'objets C ++ ont une valeur d'alignement qui restreint les adresses de leurs instances à seulement certains multiples de puissances de deux ( [basic.align]). Ces valeurs d'alignement sont définies par l'implémentation pour les types fondamentaux et n'ont pas besoin d'être égales à la taille du type. Ils peuvent également être plus stricts que ce que le matériel pourrait réellement prendre en charge.
Par exemple, x86 (principalement) prend en charge les accès non alignés. Cependant, vous trouverez la plupart des compilateurs ayant alignof(double) == sizeof(double) == 8pour x86, car les accès non alignés ont une foule d'inconvénients (vitesse, mise en cache, atomicité ...). Mais par exemple, #pragma pack(1) struct X { char a; double b; };ou alignas(1) double x;vous permet d'avoir des "non alignés" double. Ainsi, lorsque cppreference parle d '"accès à la mémoire alignés", il le fait probablement en termes d'alignement naturel du type pour le matériel, n'utilisant pas un type C ++ d'une manière qui contredit ses exigences d'alignement (qui seraient UB).
X86 32 bits est un bon exemple de l'emplacement des ABI alignof(double)==4. Mais a std::atomic<double>toujours alignof() = 8au lieu de vérifier l'alignement lors de l'exécution. L'utilisation d'une structure compactée qui sous-aligne atomique rompt l'ABI et n'est pas prise en charge. (GCC pour x86 32 bits préfère donner un alignement naturel aux objets de 8 octets, mais les règles de struct-packing l'emportent et ne sont basées que sur alignof(T), par exemple, sur le système i386 V. G ++ avait un bogue où l' atomic<int64_t>intérieur d'une structure peut ne pas être atomique car il vient de supposer. GCC (pour C pas C ++) a toujours ce bug!)
Peter Cordes
2
Mais une implémentation correcte de C ++ 20 std::atomic_ref<double>rejettera doublecomplètement sous-aligné ou vérifiera l'alignement au moment de l'exécution sur les plates-formes où il est légal pour plain doubleet int64_td'être moins que naturellement aligné. (Parce qu'il atomic_ref<T>opère sur un objet qui a été déclaré comme une plaine T, et qui n'a qu'un alignement minimum de alignof(T)sans la possibilité de lui donner un alignement supplémentaire.)
Peter Cordes
2
Voir gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 pour le bogue libstdc ++ désormais corrigé, et gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 pour le bogue C encore cassé, y compris un boîtier de test ISO C11 pur qui montre la déchirure d'un _Atomic int64_tlorsqu'il est compilé avec le courant gcc -m32. Quoi qu'il en soit, mon point est que les vrais compilateurs ne prennent pas en charge les atomiques sous-alignés, et ne font pas (encore?) De vérifications d'exécution, donc #pragma packou __attribute__((packed))mèneront simplement à la non-atomicité; les objets rapporteront toujours qu'ils le sont lock_free.
Peter Cordes du
1
Mais oui, le but de is_lock_free()est de permettre aux implémentations de fonctionner différemment de la manière actuelle; avec des contrôles d'exécution basés sur l'alignement réel pour utiliser des instructions atomiques prises en charge par HW ou pour utiliser un verrou.
is_lock_free dépend du système réel et ne peut pas être déterminé au moment de la compilation.
Explication pertinente:
Les types atomiques peuvent également être parfois sans verrouillage, par exemple si seuls les accès mémoire alignés sont naturellement atomiques sur une architecture donnée, les objets mal alignés du même type doivent utiliser des verrous.
std::numeric_limits<int>::maxdépend de l'architecture, mais est statique et constexpr. Je suppose qu'il n'y a rien de mal dans la réponse, mais je n'achète pas la première partie du raisonnement
idclev 463035818
1
Ne définit-il pas de toute façon la langue des accès non alignés pour avoir un comportement non défini de sorte qu'une évaluation de l'absence de verrouillage ou non au moment de l'exécution serait un non-sens?
Bonita Montero
1
Il n'est pas logique de décider entre les accès alignés et non alignés car le langage définit ce dernier comme un comportement non défini.
Bonita Montero
@BonitaMontero Il y a le sens "non aligné dans le sens de l'alignement des objets C ++" et "non aligné dans ce que le matériel aime". Ce ne sont pas nécessairement les mêmes, mais dans la pratique, ils le sont souvent. L'exemple que vous montrez est un exemple où le compilateur a apparemment l'hypothèse intégrée que les deux sont identiques - ce qui signifie seulement que cela is_lock_freeest inutile sur ce compilateur .
Max Langhof
1
Vous pouvez être presque sûr qu'un atomique aurait un alignement correct s'il y a une exigence d'alignement.
Bonita Montero
1
J'ai installé Visual Studio 2019 sur mon PC Windows et ce devenv a également un compilateur ARMv8. ARMv8 autorise les accès non alignés, mais la comparaison et les échanges, les ajouts verrouillés, etc. doivent être alignés. De plus, la charge pure / stockage pur utilisant ldpou stp(paire de charge ou paire de magasins de registres 32 bits) ne sont garantis atomiques que lorsqu'ils sont naturellement alignés.
J'ai donc écrit un petit programme pour vérifier ce que is_lock_free () renvoie pour un pointeur atomique arbitraire. Voici donc le code:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
C'est juste returns true, aka 1.
Cette implémentation choisit d'utiliser alignof( atomic<int64_t> ) == 8afin que chaque atomic<int64_t>soit correctement aligné. Cela évite d'avoir à effectuer des vérifications d'alignement d'exécution sur chaque chargement et chaque magasin.
(Note de l'éditeur: c'est courant; la plupart des implémentations C ++ réelles fonctionnent de cette façon. C'est pourquoi std::is_always_lock_freec'est si utile: parce que c'est généralement vrai pour les types où is_lock_free()c'est toujours vrai.)
Oui, la plupart des implémentations choisissent de donner atomic<uint64_t>et alignof() == 8n'ont donc pas à vérifier l'alignement au moment de l'exécution. Cette ancienne API leur donne la possibilité de ne pas le faire, mais sur le matériel actuel, il est beaucoup plus logique de simplement exiger l'alignement (sinon UB, par exemple la non-atomicité). Même dans le code 32 bits où l' int64_talignement sur 4 octets ne peut être requis atomic<int64_t>que sur 8 octets. Voir mes commentaires sur une autre réponse
Peter Cordes
En d'autres termes: si un compilateur choisit de faire de la alignofvaleur d'un type fondamental la même chose que le "bon" alignement du matériel, il leis_lock_free sera toujours true(et il en sera de même is_always_lock_free). Votre compilateur fait exactement cela. Mais l'API existe donc d'autres compilateurs peuvent faire des choses différentes.
Max Langhof
1
Vous pouvez être sûr que si le langage dit que l'accès non aligné a un comportement indéfini, tous les atomes doivent être correctement alignés. Aucune implémentation ne fera aucune vérification d'exécution à cause de cela.
Bonita Montero
@BonitaMontero Oui, mais il n'y a rien dans le langage qui l'interdit alignof(std::atomic<double>) == 1(il n'y aurait donc pas d '"accès non aligné" au sens C ++, donc pas d'UB), même si le matériel ne peut garantir que des opérations atomiques sans verrouillage pour doubles sur 4 ou Frontières de 8 octets. Le compilateur devrait alors utiliser des verrous dans les cas non alignés (et renvoyer la valeur booléenne appropriée is_lock_free, en fonction de l'emplacement mémoire de l'instance d'objet).
is_always_lock_free
?Réponses:
Comme expliqué sur cppreference :
Comme mentionné par plusieurs autres, c'est
std::is_always_lock_free
peut-être ce que vous recherchez vraiment.Edit: Pour clarifier, les types d'objets C ++ ont une valeur d'alignement qui restreint les adresses de leurs instances à seulement certains multiples de puissances de deux (
[basic.align]
). Ces valeurs d'alignement sont définies par l'implémentation pour les types fondamentaux et n'ont pas besoin d'être égales à la taille du type. Ils peuvent également être plus stricts que ce que le matériel pourrait réellement prendre en charge.Par exemple, x86 (principalement) prend en charge les accès non alignés. Cependant, vous trouverez la plupart des compilateurs ayant
alignof(double) == sizeof(double) == 8
pour x86, car les accès non alignés ont une foule d'inconvénients (vitesse, mise en cache, atomicité ...). Mais par exemple,#pragma pack(1) struct X { char a; double b; };
oualignas(1) double x;
vous permet d'avoir des "non alignés"double
. Ainsi, lorsque cppreference parle d '"accès à la mémoire alignés", il le fait probablement en termes d'alignement naturel du type pour le matériel, n'utilisant pas un type C ++ d'une manière qui contredit ses exigences d'alignement (qui seraient UB).Voici plus d'informations: Quel est l'effet réel des accès non alignés réussis sur x86?
Veuillez également consulter les commentaires perspicaces de @Peter Cordes ci-dessous!
la source
alignof(double)==4
. Mais astd::atomic<double>
toujoursalignof() = 8
au lieu de vérifier l'alignement lors de l'exécution. L'utilisation d'une structure compactée qui sous-aligne atomique rompt l'ABI et n'est pas prise en charge. (GCC pour x86 32 bits préfère donner un alignement naturel aux objets de 8 octets, mais les règles de struct-packing l'emportent et ne sont basées que suralignof(T)
, par exemple, sur le système i386 V. G ++ avait un bogue où l'atomic<int64_t>
intérieur d'une structure peut ne pas être atomique car il vient de supposer. GCC (pour C pas C ++) a toujours ce bug!)std::atomic_ref<double>
rejetteradouble
complètement sous-aligné ou vérifiera l'alignement au moment de l'exécution sur les plates-formes où il est légal pour plaindouble
etint64_t
d'être moins que naturellement aligné. (Parce qu'ilatomic_ref<T>
opère sur un objet qui a été déclaré comme une plaineT
, et qui n'a qu'un alignement minimum dealignof(T)
sans la possibilité de lui donner un alignement supplémentaire.)_Atomic int64_t
lorsqu'il est compilé avec le courantgcc -m32
. Quoi qu'il en soit, mon point est que les vrais compilateurs ne prennent pas en charge les atomiques sous-alignés, et ne font pas (encore?) De vérifications d'exécution, donc#pragma pack
ou__attribute__((packed))
mèneront simplement à la non-atomicité; les objets rapporteront toujours qu'ils le sontlock_free
.is_lock_free()
est de permettre aux implémentations de fonctionner différemment de la manière actuelle; avec des contrôles d'exécution basés sur l'alignement réel pour utiliser des instructions atomiques prises en charge par HW ou pour utiliser un verrou.Vous pouvez utiliser
std::is_always_lock_free
is_lock_free
dépend du système réel et ne peut pas être déterminé au moment de la compilation.Explication pertinente:
la source
std::numeric_limits<int>::max
dépend de l'architecture, mais est statique etconstexpr
. Je suppose qu'il n'y a rien de mal dans la réponse, mais je n'achète pas la première partie du raisonnementis_lock_free
est inutile sur ce compilateur .J'ai installé Visual Studio 2019 sur mon PC Windows et ce devenv a également un compilateur ARMv8. ARMv8 autorise les accès non alignés, mais la comparaison et les échanges, les ajouts verrouillés, etc. doivent être alignés. De plus, la charge pure / stockage pur utilisant
ldp
oustp
(paire de charge ou paire de magasins de registres 32 bits) ne sont garantis atomiques que lorsqu'ils sont naturellement alignés.J'ai donc écrit un petit programme pour vérifier ce que is_lock_free () renvoie pour un pointeur atomique arbitraire. Voici donc le code:
Et c'est le démontage de isLockFreeAtomic
C'est juste
returns true
, aka1
.Cette implémentation choisit d'utiliser
alignof( atomic<int64_t> ) == 8
afin que chaqueatomic<int64_t>
soit correctement aligné. Cela évite d'avoir à effectuer des vérifications d'alignement d'exécution sur chaque chargement et chaque magasin.(Note de l'éditeur: c'est courant; la plupart des implémentations C ++ réelles fonctionnent de cette façon. C'est pourquoi
std::is_always_lock_free
c'est si utile: parce que c'est généralement vrai pour les types oùis_lock_free()
c'est toujours vrai.)la source
atomic<uint64_t>
etalignof() == 8
n'ont donc pas à vérifier l'alignement au moment de l'exécution. Cette ancienne API leur donne la possibilité de ne pas le faire, mais sur le matériel actuel, il est beaucoup plus logique de simplement exiger l'alignement (sinon UB, par exemple la non-atomicité). Même dans le code 32 bits où l'int64_t
alignement sur 4 octets ne peut être requisatomic<int64_t>
que sur 8 octets. Voir mes commentaires sur une autre réponsealignof
valeur d'un type fondamental la même chose que le "bon" alignement du matériel, il leis_lock_free
sera toujourstrue
(et il en sera de mêmeis_always_lock_free
). Votre compilateur fait exactement cela. Mais l'API existe donc d'autres compilateurs peuvent faire des choses différentes.alignof(std::atomic<double>) == 1
(il n'y aurait donc pas d '"accès non aligné" au sens C ++, donc pas d'UB), même si le matériel ne peut garantir que des opérations atomiques sans verrouillage pourdouble
s sur 4 ou Frontières de 8 octets. Le compilateur devrait alors utiliser des verrous dans les cas non alignés (et renvoyer la valeur booléenne appropriéeis_lock_free
, en fonction de l'emplacement mémoire de l'instance d'objet).