5

Résumé: Je m'attendais à ce que std::atomic<int*>::load avec std::memory_order_relaxed serait proche de la performance de charger un pointeur directement, au moins lorsque la valeur chargée change rarement. J'ai vu une performance bien pire pour la charge atomique qu'une charge normale sur Visual Studio C++ 2012, j'ai donc décidé d'enquêter. Il s'avère que la charge atomique est implémentée sous la forme d'une boucle compare-and-swap, ce qui, à mon avis, n'est pas l'implémentation la plus rapide possible.Est ce que std :: atomic <int*> :: load devrait faire une boucle compare-and-swap?

Question: Y at-il une raison pour laquelle std::atomic<int*>::load doit effectuer une boucle de comparaison et d'échange?

Contexte: Je crois que MSVC++ 2012 fait une boucle comparer et-échange sur la charge atomique d'un pointeur basé sur ce programme de test:

#include <atomic> 
#include <iostream> 

template<class T> 
__declspec(noinline) T loadRelaxed(const std::atomic<T>& t) { 
    return t.load(std::memory_order_relaxed); 
} 

int main() { 
    int i = 42; 
    char c = 42; 
    std::atomic<int*> ptr(&i); 
    std::atomic<int> integer; 
    std::atomic<char> character; 
    std::cout 
    << *loadRelaxed(ptr) << ' ' 
    << loadRelaxed(integer) << ' ' 
    << loadRelaxed(character) << std::endl; 
    return 0; 
} 

J'utilise une fonction __declspec(noinline) pour isoler les instructions d'assemblage relatives à la charge atomique. J'ai fait un nouveau projet MSVC++ 2012, ajouté une plate-forme x64, sélectionné la configuration de la version, exécuté le programme dans le débogueur et examiné le désassemblage. Il s'avère que les deux paramètres std::atomic<char> et std::atomic<int> finissent par donner le même appel à loadRelaxed<int> - ce doit être quelque chose que l'optimiseur a fait. Voici le démontage des deux instanciations loadRelaxed qui s'appelle:

loadRelaxed<int * __ptr64>

000000013F4B1790 prefetchw [rcx] 
000000013F4B1793 mov   rax,qword ptr [rcx] 
000000013F4B1796 mov   rdx,rax 
000000013F4B1799 lock cmpxchg qword ptr [rcx],rdx 
000000013F4B179E jne   loadRelaxed<int * __ptr64>+6h (013F4B1796h) 

loadRelaxed<int>

000000013F3F1940 prefetchw [rcx] 
000000013F3F1943 mov   eax,dword ptr [rcx] 
000000013F3F1945 mov   edx,eax 
000000013F3F1947 lock cmpxchg dword ptr [rcx],edx 
000000013F3F194B jne   loadRelaxed<int>+5h (013F3F1945h) 

L'instruction lock cmpxchg est atomique compare-and-swap et nous voyons ici que le code pour charger atomiquement un char, un int ou int* est une boucle de comparaison et d'échange. J'ai également construit ce code pour 32 bits x86 et cette implémentation est toujours basée sur lock cmpxchg.

Question: Y at-il une raison pour laquelle std::atomic<int*>::load doit effectuer une boucle de comparaison et d'échange?

+0

Je voudrais aussi voir pourquoi ce type de code est généré – James

+0

@James Je pense que MS n'a tout simplement pas encore eu le temps de l'implémenter. De mon propre effort d'implémentation, il n'a fallu qu'une petite quantité de code pour accélérer le processus, mais il a fallu beaucoup d'efforts pour comprendre en détail ce que ce code était supposé faire et comment cela interagissait avec une plate-forme matérielle donnée. J'ai dépendu de matériel écrit par d'autres personnes, mais pour être * vraiment * sûr que vous l'ayez fait correctement, je pense qu'il serait nécessaire de contacter les vendeurs de matériel et de passer beaucoup de temps sur la norme. Compare-and-swap est beaucoup plus facile à faire et certainement correct. –

+0

Voir http://connect.microsoft.com/VisualStudio/commentaires/détails/770885/std-atomic-load-implementation-is-absurdly-slow –

Répondre

1

Je ne crois pas que les charges atomiques relâchées nécessitent une comparaison et un échange. Au final, cette implémentation std :: atomic n'était pas utilisable pour mon but, mais je voulais quand même avoir l'interface, donc j'ai fait mon propre std :: atomic en utilisant les intrinsèques de la barrière de MSVC. Cela a de meilleures performances que la valeur par défaut std::atomic pour mon cas d'utilisation. Vous pouvez voir le code here. Il est supposé être implémenté à la spécification C++ 11 pour toutes les commandes de chargement et de stockage. Btw GCC 4.6 n'est pas meilleur à cet égard. Je ne sais pas à propos de GCC 4.7.

Questions connexes