Zusammenfassung: Ich hatte erwartet, dass std::atomic<int*>::load
mit std::memory_order_relaxed
nahe an der Leistung wäre, nur einen Zeiger direkt zu laden, zumindest wenn sich der geladene Wert selten ändert. Ich sah weit schlechtere Leistung für die atomare Last als eine normale Last auf Visual Studio C++ 2012, also entschied ich mich zu untersuchen. Es stellt sich heraus, dass die atomare Last als eine compare-and-swap-Schleife implementiert ist, die vermutlich nicht die schnellste Implementierung ist.Sollte std :: atomic <int*> :: Laden eine Vergleichs- und Tausch-Schleife tun?
Frage: Gibt es einen Grund, dass std::atomic<int*>::load
eine Vergleichs- und Tauschschleife durchführen muss?
Hintergrund: Ich glaube, dass MSVC++ 2012 ist eine Vergleichs- und Swap-Schleife auf atomare Last eines basierend auf diesem Testprogramm Zeiger zu tun: Um
#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;
}
Ich verwende eine __declspec(noinline)
Funktion in um die Montageanweisungen bezüglich der atomaren Belastung zu isolieren. Ich habe ein neues MSVC++ 2012-Projekt erstellt, eine x64-Plattform hinzugefügt, die Release-Konfiguration ausgewählt, das Programm im Debugger ausgeführt und die Disassemblierung betrachtet. Stellt sich heraus, dass beide std::atomic<char>
und std::atomic<int>
Parameter am Ende geben den gleichen Anruf an - dies muss etwas sein, das der Optimierer getan hat. Hier ist die Demontage der beiden loadRelaxed instantiations, die aufgerufen:
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)
Die Anweisung lock cmpxchg
Atom ist compare-and-swap und wir sehen hier, dass der Code zum atomaren Laden einer char
, einer int
oder int*
ist eine Compare-and-Swap-Schleife. Ich habe auch diesen Code für 32-Bit x86 gebaut und diese Implementierung basiert immer noch auf lock cmpxchg
.
Frage: Gibt es einen Grund, dass std::atomic<int*>::load
eine Vergleichs- und Tauschschleife durchführen muss?
Ich würde auch gerne sehen, warum diese Art von Code generiert wird – James
@James Ich vermute, dass MS hat noch nicht die Zeit, dies besser zu implementieren. Aus meiner eigenen Implementierung heraus dauerte es nur eine winzige Menge an Code, um dies schneller zu machen, aber es brauchte viel Aufwand, um im Detail zu verstehen, was dieser Code tun sollte und wie er mit einer bestimmten Hardware-Plattform interagiert. Ich bin auf Material angewiesen, das von anderen Leuten geschrieben wurde, aber um * wirklich * sicher zu sein, dass du es richtig gemacht hast, denke ich, dass es notwendig sein würde, mit Hardwareherstellern in Kontakt zu treten und viel Zeit mit dem Studium des Standards zu verbringen. Compare-and-Swap ist viel einfacher zu machen und sicherlich zu korrigieren. –
Siehe http://connect.microsoft.com/VisualStudio/Feedback/Details/770885/std-Atomic-Load-Implementierung-ist-absurd-langsam –