2011-07-25 10 views
8

Napisałem trochę kodu wolnego od blokady, który działa dobrze z lokalnym odczytem , w większości przypadków.Czy blokady spinów zawsze wymagają zapory pamięci? Czy wirowanie na barierce pamięci jest drogie?

Czy lokalne przestawianie się na pamięć wymaga koniecznie oznaczać, że I musi ZAWSZE wstawiać barierę pamięci przed odczytaniem?

(Aby to potwierdzić, udało mi się stworzyć odczytu/zapisu kombinację, która prowadzi do czytnika nie widząc pisemnej WARTOŚĆ w pewnych ściśle określonych warunkach - dedykowany procesor, procesowych dołączonych do CPU, optymalizator odwrócił aż do góry, żadna inna praca wykonywana w pętli - tak strzałki zrobić punkt w tym kierunku, ale nie jestem do końca pewien o kosztach przędzenia za pomocą pamięci barierę)

co jest. koszt prześlizgnięcia się przez barierę pamięci, jeśli nie ma nic do przepłukania w magazynie pamięci podręcznej e bufora? czyli cały proces robi (w C) jest

while (1) { 
    __sync_synchronize(); 
    v = value; 
    if (v != 0) { 
     ... something ... 
    } 
} 

Am I słusznie przypuszczać, że to nic nie kosztuje i nie będzie obciążać magistrali pamięci z dowolnego ruchu?

Innym sposobem, aby umieścić to jest pytanie: czy bariera pamięć zrobić nic więcej niż: opróżnić bufor sklepu, stosują unieważnień do niego i zapobiec kompilator z ponownego zamawiania odczytuje/zapisuje w poprzek jego miejscu?


Demontaż, __sync_synchronize() pojawia się przekładać na:

lock orl 

z podręcznika Intel (podobnie chaotyczny dla neofity):

Volume 3A: System Programming Guide, Part 1 -- 8.1.2 

Bus Locking 

Intel 64 and IA-32 processors provide a LOCK# signal that 
is asserted automatically during certain critical memory 
operations to lock the system bus or equivalent link. 
While this output signal is asserted, requests from other 
processors or bus agents for control of the bus are 
blocked. 

[...] 

For the P6 and more recent processor families, if the 
memory area being accessed is cached internally in the 
processor, the LOCK# signal is generally not asserted; 
instead, locking is only applied to the processor’s caches 
(see Section 8.1.4, “Effects of a LOCK Operation on 
Internal Processor Caches”). 

moje tłumaczenie: „Kiedy mówisz LOCK, byłoby to drogie, ale robimy to tylko wtedy, gdy jest to konieczne. "


@BlankXavier:

Zrobiłem test, czy autor wyraźnie nie wypchnąć zapisu z bufora sklepie i jest to jedyny proces uruchomiony na tym CPU, czytelnik może nigdy zobaczyć efekt pisarza (mogę go odtworzyć za pomocą programu testowego, ale jak już wspomniałem powyżej, dzieje się to tylko przy określonym teście, z konkretnymi opcjami kompilacji i dedykowanymi zadaniami rdzeniowymi - mój algorytm działa dobrze, to tylko wtedy, gdy jestem ciekawy o tym, jak to działa i napisałem test jawny, który zdałam sobie sprawę, że może potencjalnie mieć problem na drodze).

Myślę, że domyślnie proste zapisy są zapisywane WB (Write Back), co oznacza, że ​​nie są natychmiast wypłukiwane, ale odczyty przyjmują ich najnowszą wartość (myślę, że nazywają to "przekazywaniem do sklepu"). Używam więc instrukcji CAS dla pisarza. W podręczniku Intela odkryłem wszystkie te różne typy implementacji zapisu (UC, WC, WT, WB, WP), Intel vol 3A, rozdział 11-10, wciąż się o nich ucząc.

Moja niepewność jest po stronie czytelnika: Rozumiem z pracy McKenneya, że ​​istnieje również kolejka unieważnień, kolejka przychodzących unieważnień z magistrali do pamięci podręcznej. Nie jestem pewien, jak działa ta część. W szczególności, wydajesz się sugerować, że zapętlenie przez normalny odczyt (tj. Niezablokowany, bez bariery i używanie tylko lotnego, by zapewnić optymalizatorowi pozostawienie skompilowanego odczytu) będzie sprawdzać w "kolejce unieważniania" za każdym razem (jeśli coś takiego istnieje). Jeśli prosty odczyt nie jest wystarczająco dobry (tj. Mógłby odczytać starą linię pamięci podręcznej, która nadal wydaje się być poprawna w oczekiwaniu na kolejkę unieważnienia (co brzmi dla mnie nieco niespójnie, ale jak działają kolejki unieważniające?)), To odczyt atomowy konieczne i moje pytanie brzmi: czy w tym przypadku będzie to miało jakikolwiek wpływ na autobus? (Myślę, że prawdopodobnie nie.)

Ciągle czytam moją instrukcję Intel i podczas gdy widzę wspaniałą dyskusję na temat przekazywania sklepu, nie znalazłem dobrej dyskusji o kolejkach unieważnień. Postanowiłem przekonwertować kod C na ASM i eksperymentować, uważam, że jest to najlepszy sposób, aby naprawdę poczuć, jak to działa.

+3

"Działa dobrze z lokalnymi odczytami, w większości przypadków". - jeśli to nie działa "dobrze" zawsze, to nie jest dobrze ..... –

+0

Jeśli chodzi o test małej pętli z pełną optymalizacją, istnieją inne problemy, np. [Błąd w komorze Cyrixa] (http://en.wikipedia.org/wiki/Cyrix_coma_bug#Analysis) (nawet jeśli nie ma to zastosowania w tym przypadku), co może mieć wpływ na "fałszywe" testy. –

+0

@Mitch: moje, oczywiście, dlatego pytam :-) – blais

Odpowiedz

2

I nie może właściwie dobrze zrozumiałem pytanie, ale ...

Jeśli spinning, jeden problem to kompilator optymalizacji wirowania dala. Volatile rozwiązuje to.

Bariera pamięci, jeśli ją masz, zostanie wydana przez pisarza do blokady spin, a nie czytnika. W rzeczywistości pisarz tak naprawdę nie jest w stanie go użyć, ponieważ zapewnia, że ​​zapis zostanie natychmiast usunięty, ale i tak wkrótce zniknie.

Bariera zapobiega wątkowi wykonującemu ten sam kod, który zmienia kolejność w jego lokalizacji, co jest jego drugim kosztem.

+0

odpowiedziałem w powyższej edycji, SO nie pozwoli mi dodać długiego komentarza. – blais

4

Instrukcja "xchg reg, [mem]" zasygnalizuje zamiar zamka nad kołkiem LOCK rdzenia. Ten sygnał przeplata się z innymi rdzeniami i pamięcią podręczną do magistrali bus-mastering (warianty PCI itp.), Które zakończą to, co robią i ostatecznie pin LOCKA (potwierdzający) zasygnalizuje procesorowi, że Xchg może zakończyć. Następnie sygnał LOCK jest wyłączony. Ta sekwencja może zająć dużo czasu (setki cykli procesora lub więcej), aby zakończyć. Następnie odpowiednie linie pamięci podręcznej innych rdzeni zostaną unieważnione, a będziesz miał znany stan, tj. Taki, który ma synchronizację między rdzeniami.

Instrukcja xchg to wszystko, co jest potrzebne do wdrożenia blokady atomowej. Jeśli sama blokada się powiedzie, masz dostęp do zasobu, w którym zdefiniowałeś blokadę, aby kontrolować dostęp. Takim zasobem może być obszar pamięci, plik, urządzenie, funkcja lub co ty. Jednak programista zawsze musi napisać kod, który używa tego zasobu, gdy jest zablokowany i nie ma go, gdy nie jest. Zazwyczaj sekwencja kodu następująca po pomyślnym zablokowaniu powinna być tak krótka, jak to możliwe, tak, aby inny kod był jak najmniej utrudniony w uzyskaniu dostępu do zasobu.

Należy pamiętać, że jeśli blokada się nie powiodła, należy spróbować ponownie, wydając nowe xchg.

"Zablokuj za darmo" jest atrakcyjną koncepcją, ale wymaga eliminacji współdzielonych zasobów. Jeśli twoja aplikacja ma dwa lub więcej rdzeni jednocześnie odczyt i zapis do wspólnego adresu pamięci "lock free" nie jest opcją.

0

Należy pamiętać, że bariery są zazwyczaj używane do zamawiania zestawów dostępu do pamięci, więc Twój kod najprawdopodobniej będzie również wymagał barier w innych miejscach.Na przykład, nie byłoby niczym niezwykłym wymóg barierą wyglądać tak zamiast:

while (1) { 

    v = pShared->value; 
    __acquire_barrier() ; 

    if (v != 0) { 
     foo(pShared->something) ; 
    } 
} 

Bariera ta uniemożliwi obciążenia i zapisanie ich w bloku if (tj: pShared->something) z wykonaniem przed obciążenie value jest kompletna . Typowym przykładem jest to, że masz jakieś „producent”, który używany zapas v != 0 do flaga że inna pamięć (pShared->something) jest w jakimś innym stanie oczekiwanym, jak w:

pShared->something = 1 ; // was 0 
__release_barrier() ; 
pShared->value = 1 ; // was 0 

W tym typowym scenariuszu konsumentów producentów, prawie zawsze potrzebujesz parowanych barier, jednego dla sklepu, który zaznacza, że ​​pamięć pomocnicza jest widoczna (tak, że efekty magazynu wartości nie są widoczne przed składowaniem czegoś) i jedna bariera dla konsumenta (tak, że ładowanie czegoś nie rozpoczyna się przed zakończeniem ładowania wartości).

Bariery te są również specyficzne dla platformy. Na przykład na powerpc (przy użyciu kompilatora xlC), użyjesz odpowiednio __isync() i __lwsync() dla konsumenta i producenta. Jakie bariery są wymagane, może również zależeć od mechanizmu, którego używasz do przechowywania i ładowania value. Jeśli użyłeś wewnętrznego atomu, który daje w wyniku intel LOCK (być może implicite), wprowadzi to domyślną barierę, więc możesz niczego nie potrzebować. Dodatkowo, będziesz prawdopodobnie musiał również rozważyć użycie niestabilnego (lub najlepiej użyć implementacji atomowej, która robi to pod osłonami), aby kompilator robił to, co chcesz.

Powiązane problemy