C++11 de la mise en Œuvre de Spinlock à l'aide de <atomic>
J'ai mis en place SpinLock classe, comme suit
struct Node {
int number;
std::atomic_bool latch;
void add() {
lock();
number++;
unlock();
}
void lock() {
bool unlatched = false;
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire));
}
void unlock() {
latch.store(false , std::memory_order_release);
}
};
J'ai mis en place au-dessus de la classe et fait deux threads appel de méthode add() d'une instance de classe de Nœud de 10 millions de fois par thread.
le résultat est , malheureusement, pas plus de 20 millions de dollars.
Ce qui me manque ici?
- Soyez averti, c'est la pire spinlock vous pouvez éventuellement mettre en œuvre. Parmi les autres problèmes: 1) Lorsque vous enfin faire acquérir les
lock
, vous prenez la mère de toutes les mispredicted branches en laissant lawhile
boucle, ce qui est le pire moment possible pour une telle chose. 2) Lalock
fonction peut affamer l'autre thread en cours d'exécution dans la même cœur virtuel sur un serveur hyper-thread CPU. - Merci pour vos commentaires. Puis-je demander de plus sur les problèmes que vous avez évoqués? 2). Que la fonction de verrouillage peut mourir de faim un autre thread, (c'est vrai!, et il est destiné à faire depuis que je suis sûr que le verrouillage de la durée de vie est très courte). Je peux utiliser des spin compteurs est ce problème, ai-je le droit? 1). Pourquoi je prendre "la mère de toutes les mispredicated brabches" dans ce code?, et comment puis-je l'améliorer?? avez-vous des commentaires à ce sujet? Merci encore
- 2) Pas. Aucune de ces choses résoudre le problème. À l'aide de la
pause
instruction n'. 1) Parce que le CPU prédit lewhile
boucle pour garder la boucle. Et, comme il arrive, vous pouvez également corriger cela avec lapause
instruction. - J'ai obtenu ce que vous voulez dire. Peut-être que j'ai besoin de quelques recherches sur google pour comprendre comment "pause" fonctionne et comment éviter les erreurs de prédiction de branche.
- Pause permet spéculative de l'exécution, en éliminant les erreurs de prédiction de la direction générale de la peine. (Et, en passant, si vous ne connaissez pas ce genre de trucs à l'intérieur, vous n'avez rien écrit spinlocks. Vous pourrez faire toutes les erreurs et ne pas réaliser que vous avez eu d'autres choix.)
- Je suis intéressé par l'écriture de ces sortes de choses, mais je ne sais pas ce genre de choses à l'intérieur. Pouvez-vous recommander la façon dont une personne peut se faire connaître cet intérieur?
- Je n'ai pas vraiment une bonne réponse à cela, d'autres que d'étudier soigneusement la qualité du travail des autres et d'acquérir beaucoup d'expérience. Ce n'est pas vraiment une piscine amateurs peuvent jouer, au-delà de l'écriture jouet code. Je me suis seulement en savons assez pour être dangereux. 😉
- Il semble que la seule façon de devenir suffisamment compétent pour être autorisé à essayer d'écrire ce genre de code est d'essayer d'écrire ce genre de code.
- Eh bien, il n'y a jamais de mal à essayer de faire tout type de codage. Juste avoir des attentes réalistes pour la qualité de ce que vous produisez et ne vous leurrez pas en pensant que vous êtes en train de faire, sauf si vous êtes. Dans une certaine mesure, c'est comme rouler votre propre chiffrement. Il est très facile à faire mal et parfois très difficile à point de ce qui est mauvais à propos d'un effort particulier. Beaucoup trop de gens pensent que c'est facile ou ce que vous devriez faire.
- Qui le verrou de rotation de la mise en œuvre recommanderiez-vous? Ni les mst ni boost offre une, de sorte qu'il n'est pas évident. La folie a quelques choses, google a certaines choses, elles offrent toutes une bonne quantité de plus que ce qui est strictement nécessaire. Je serais intéressé par un simple verrou de rotation qui pourrait inclure la pause, aussi bien comme quelque chose d'un peu plus élaboré qui fournit également un bool is_locked fonction, basée sur std::atomic<bool> par exemple.
- Vous pouvez en écrire un pour un CPU et de la plate-forme. Vous ne pouvez pas écrire un portable. Par exemple, sur les systèmes x86, vous devez émettre un
pause
instruction (rep nop
) dans la boucle interne. Il n'existe pas de portable façon de le faire-vous avez besoin de CPU expertise.
Vous devez vous connecter pour publier un commentaire.
Le problème est que
compare_exchange_weak
les mises à jour de launlatched
variable une fois qu'il échoue. À partir de la documentation decompare_exchange_weak
:I. e., après le premier échec
compare_exchange_weak
,unlatched
sera mis à jour pourtrue
, de sorte que la prochaine itération de boucle va essayer decompare_exchange_weak
true
avectrue
. Cela réussit et que vous venez de prendre un verrou qui était détenu par un autre thread.Solution:
Assurez-vous de définir
unlatched
retour àfalse
avant chaquecompare_exchange_weak
, par exemple:Node
sera pas valeur d'initialisation denumber
que présenté. I. e.Node node;
ne laissera pas une valeur déterministe dansnode.number
pour démarrer votre séquence. Vous avez besoin d'un constructeur d'une telle utilisation,Node() : number() {}
est suffisant. le voir en direct__mm_pause()
pour permettre hyperthreads.Comme mentionné par @gexicide, le problème est que le
compare_exchange
fonctions de mise à jour de laexpected
variable avec la valeur actuelle de la variable atomique. C'est aussi la raison pour laquelle vous devez utiliser la variable localeunlatched
en premier lieu. Pour résoudre ce problème, vous pouvez définirunlatched
dos à faux dans chaque itération de boucle.Cependant, au lieu d'utiliser
compare_exchange
pour quelque chose de son interface est plutôt mal adapté, il est beaucoup plus simple à utiliserstd::atomic_flag
à la place:Source: cppreference
Spécifiant manuellement la mémoire de la commande est de seulement un faible potentiel de rendement tweak, dont j'ai copié à partir de la source. Si la simplicité est plus important que le dernier bit de la performance, vous pouvez coller aux valeurs par défaut et il suffit d'appeler
locked.test_and_set() /locked.clear()
.Btw.:
std::atomic_flag
est le seul type qui est garanti pour être lock gratuit, bien que je ne sais pas de n'importe quelle plateforme, où oparations surstd::atomic_bool
ne sont pas lock gratuit.Mise à jour: Comme expliqué dans les commentaires de @David Schwartz, @Anton et @Technik Empire, le vide de la boucle a certains effets indésirables comme la branche missprediction, de l'insuffisance de thread sur HT transformateurs et trop forte consommation d'énergie - donc, en bref, c'est un joli moyen inefficace pour attendre. L'impact et la solution sont l'architecture, la plate-forme et les applications spécifiques. Je ne suis pas expert, mais l'habitude solution semble être d'ajouter un
cpu_relax()
sur linux ouYieldProcessor()
sur windows pour le corps de la boucle.EDIT2: Juste pour être clair: La version portable présentés ici (sans cpu_relax etc instructions) devrait déjà être assez bon pour de nombreuses applications. Si votre
SpinLock
tourne beaucoup, parce que quelqu'un d'autre est maintenant le verrou pour une longue période (ce qui peut déjà indiquer une conception générale de problème), il est probablement préférable d'utiliser une normale mutex de toute façon.cpu_relax();
à l'intérieur de la boucle while et for windows il suffit de ne#define cpu_relax() asm volatile("pause" ::: "memory")
. +1 si.std::this_thread::yield()
est assez lourd appel système, donc je ne suis pas sûr que si je l'avais mis dans le corps de la boucle de spinlock. Mon (non testé) hypothèse est que, dans la plupart des situations, où que ce serait OK, vous souhaitez utiliser un std::mutex (ou similaire) pour commencer.