2012-11-03 10 views
5

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?

+0

Chciałbym również zobaczyć, dlaczego ten rodzaj kodu jest generowany – James

+0

@ 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. –

+0

Zobacz http://connect.microsoft.com/VisualStudio/feedback/details/770885/std-atomic-load-implementation-is-absurdalnie-slow –

Odpowiedz

1

Nie sądzę, że luźne ładunki atomowe wymagają porównania i zamiany. W końcu ta implementacja std :: atomic nie nadawała się do mojego celu, ale nadal chciałem mieć interfejs, więc stworzyłem własny std :: atomic używając wewnętrznej bariery MSVC. To ma lepszą wydajność niż domyślny std::atomic dla mojego przypadku użycia. Możesz zobaczyć kod here. Powinien być zaimplementowany do specyfikacji C++ 11 dla wszystkich zamówień na ładowanie i przechowywanie. Btw GCC 4.6 nie jest lepszy pod tym względem. Nie wiem o GCC 4.7.

Powiązane problemy