Podsumowanie: Oczekiwałem, że std::atomic<int*>::load
z std::memory_order_relaxed
będzie zbliżone do wydajności właśnie ładującego wskaźnik bezpośrednio, przynajmniej gdy załadowana wartość rzadko się zmienia. Widziałem znacznie gorszą wydajność dla obciążenia atomowego niż normalne obciążenie w Visual Studio C++ 2012, więc postanowiłem zbadać. Okazuje się, że ładunek atomowy jest zaimplementowany jako pętla compare-and-swap, co, jak podejrzewam, nie jest najszybszą możliwą implementacją.Czy std :: atomic <int*> :: load powinien wykonywać pętlę porównywania i zamiany?
Pytanie: Czy jest jakiś powód, dla którego std::atomic<int*>::load
musi wykonać pętlę porównania i wymiany?
Tło: Wierzę, że MSVC++ 2012 robi pętlę porównać-i-swap na obciążenia atomowej wskaźnik na podstawie tego programu testowego:
#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;
}
Używam __declspec(noinline)
funkcji w celu odizolować instrukcje montażu związane z ładunkiem atomowym. Zrobiłem nowy projekt MSVC++ 2012, dodałem platformę x64, wybrałem konfigurację wydania, uruchomiłem program w debugerze i przyjrzałem się dezasemblacji. Okazuje się, że oba parametry kończą się na tym samym połączeniu z loadRelaxed<int>
- musi to być coś, co zrobił optymalizator. Oto demontaż dwóch loadRelaxed dawałaby które się nazywa:
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)
Dyspozycja lock cmpxchg
jest atomowy compare-and-swap i widzimy tutaj, że kod dla atomistycznego ładowania char
, int
lub int*
to pętla porównania i zamiany. Zbudowałem również ten kod dla 32-bitowego x86 i ta implementacja nadal opiera się na lock cmpxchg
.
Pytanie: Czy jest jakiś powód, dla którego std::atomic<int*>::load
musi wykonać pętlę porównania i wymiany?
Chciałbym również zobaczyć, dlaczego ten rodzaj kodu jest generowany – James
@ James Podejrzewam, że MS nie ma jeszcze czasu, aby to lepiej wdrożyć. Z moich własnych wysiłków związanych z wdrożeniem, wymagało to tylko niewielkiej ilości kodu, aby przyspieszyć ten proces, ale wymagało to dużego wysiłku, aby dokładnie zrozumieć, co ten kod powinien robić i jak wchodzi on w interakcje z daną platformą sprzętową. Zależało mi na materiałach napisanych przez inne osoby, ale aby być naprawdę pewnym, że zrobiłeś to dobrze, wydaje mi się, że konieczne byłoby skontaktowanie się ze sprzedawcami sprzętu i poświęcenie dużo czasu na zgłębianie standardu. Porównywanie i zamienianie jest o wiele łatwiejsze i na pewno poprawne. –
Zobacz http://connect.microsoft.com/VisualStudio/feedback/details/770885/std-atomic-load-implementation-is-absurdalnie-slow –