Jaka jest lepsza bariera zapisu na x86: lock+addl czy xchgl?

Jądro Linuksa używa lock; addl $0,0(%%esp) jako bariery zapisu, podczas gdy biblioteka RE2 używa xchgl (%0),%0 jako bariery zapisu. Jaka jest różnica i która jest lepsza?

Czy x86 wymaga również instrukcji read barrier? RE2 definiuje swoją funkcję bariery odczytu jako no-op NA x86, podczas gdy Linux definiuje ją jako lfence lub no-op w zależności od tego, czy SSE2 jest dostępny. Kiedy jest wymagane lfence?

Author: Hongli, 2010-11-20

5 answers

" lock; addl $0,0(%%esp)" jest szybszy w przypadku, gdy testujemy stan 0 zmiennej lock pod adresem (%%esp). Ponieważ dodajemy wartość 0 do zmiennej lock i flaga zero jest ustawiona na 1, jeśli wartość blokady zmiennej pod adresem (%%esp) wynosi 0.


Lfence from Intel datasheet:

Wykonuje operację serializacji na wszystkie instrukcje load-from-memory, które zostały wydane przed LFENCE Instrukcja. Ten serializujący operacja gwarantuje, że każde obciążenie Instrukcja poprzedzająca w programie zamówienie instrukcji LFENCE jest globalnie widoczne przed każdym obciążeniem instrukcja, która następuje po LFENCE instrukcja jest widoczna globalnie.

(Nota wydawcy: mfence lub operacja locked jest jedynym użytecznym ogrodzeniem (po sklepie) dla sekwencyjnej spójności . lfence Czy nie blokuje zmianę kolejności obciążenia magazynu przez bufor sklepu.)


Na przykład: instrukcja zapisu pamięci jak ' mov ' są atomowe (nie potrzebują prefiksu lock), jeśli są odpowiednio wyrównane. Ale ta instrukcja jest normalnie wykonywana w pamięci podręcznej CPU i nie będzie globalnie widoczna w tym momencie dla wszystkich innych wątków, ponieważ ogrodzenie pamięci musi być najpierw wykonane, aby ten wątek poczekał, aż poprzednie sklepy będą widoczne dla innych wątków.


Więc główna różnica między tymi dwoma instrukcjami jest taka, żeInstrukcja xchgl nie będzie miała żadnego wpływu na flagi warunkowe. Z pewnością możemy przetestować stan zmiennej lock za pomocą instrukcji lock cmpxchg, ale jest to jeszcze bardziej skomplikowane niż instrukcja lock add $0.

 9
Author: GJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-19 01:15:54

Cytowanie z podręczników IA32 (Vol 3A, Rozdział 8.2: porządkowanie pamięci):

W systemie jednoprocesorowym dla regionów pamięci zdefiniowanych jako buforowalne odpisywanie, model porządkowania pamięci przestrzega następujących zasad [..]

  • odczyty nie są zmieniane z innymi odczytami
  • zapisy nie są zmieniane ze starszymi odczytami
  • zapis do pamięci nie jest zmieniany z innymi zapisami, z wyjątkiem
    • pisze za pomocą CLFLUSH Instrukcja
    • streaming przechowuje (zapisuje) wykonywane z nie-czasowymi instrukcjami ruchu ([lista instrukcji tutaj])
    • operacje na łańcuchach (patrz sekcja 8.2.4.1)
  • odczyty mogą być zmieniane ze starszymi zapisami w różnych lokalizacjach, ale nie ze starszymi zapisami w tej samej lokalizacji.
  • Nie można zmienić kolejności odczytu ani zapisu za pomocą instrukcji We/Wy, instrukcji zablokowanych lub instrukcji serializujących.]}
  • odczyty nie mogą przejść LFENCE i MFENCE instrukcje
  • zapis nie może przekazać SFENCE i MFENCE instrukcji

Uwaga: "w systemie jednoprocesorowym" powyżej jest nieco mylące. Te same reguły obowiązują dla każdego (logicznego) procesora z osobna; podręcznik następnie opisuje dodatkowe reguły porządkowania między wieloma procesorami. Jedynym fragmentem tego pytania jest to, że

  • zablokowane instrukcje mają całkowitą kolejność.

W skrócie, jak tak długo, jak piszesz do pamięci odpisowej (która jest całą pamięcią, jaką kiedykolwiek zobaczysz, dopóki nie jesteś sterownikiem lub programistą Grafiki), większość instrukcji x86 jest prawie sekwencyjnie spójna - jedyna zmiana kolejności procesora x86 może wykonać to zmiana kolejności późniejszego (niezależnego) odczytu do wykonania przed zapisem. Najważniejsze w barierach zapisu jest to, że mają prefiks lock (implicit lub explicit), który zabrania zmiany kolejności i zapewnia, że operacje są postrzegane w tej samej kolejności przez wszystkich procesory w systemie wieloprocesorowym.

Również w pamięci odpisowej odczyty nigdy nie są zmieniane, więc nie ma potrzeby stosowania barier odczytu. Najnowsze procesory x86 mają słabszy model konsystencji pamięci dla sklepów strumieniowych i pamięci łączonej z zapisem (powszechnie używany do zmapowanej pamięci graficznej). W tym miejscu pojawiają się różne instrukcje fence; nie są one konieczne dla żadnego innego typu pamięci, ale niektóre sterowniki w jądrze Linuksa zajmują się pamięcią połączoną z zapisem, więc po prostu zdefiniowały ich bariera odczytu w tamtą stronę. Lista modeli porządkowych według typu pamięci znajduje się w sekcji 11.3.1 w Vol. 3A podręczników IA-32. Wersja skrócona: możliwość zapisu, zapisu i zapisu umożliwia odczyt spekulatywny (zgodnie z zasadami opisanymi powyżej), nieosiągalna i mocna pamięć nieosiągalna ma silne Gwarancje porządkowania (brak zmiany kolejności procesora, odczyty / zapisy są natychmiast wykonywane, używane dla MMIO), a pamięć łączona zapisu ma słabe porządkowanie (tzn. ogrodzenia).

 10
Author: Fabian Giesen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-20 22:00:22

lock addl $0, (%esp) jest substytutem mfence, a nie lfence.

Przypadek użycia polega na tym, że musisz zablokować zmianę kolejności w StoreLoad (jedyny taki, na jaki pozwala model silnej pamięci x86), ale nie potrzebujesz operacji atomic RMW na współdzielonej zmiennej. https://preshing.com/20120515/memory-reordering-caught-in-the-act/

Np. zakładając wyrównanie std::atomic<int> a,b:

movl   $1, a             a = 1;    Atomic for aligned a
# barrier needed here
movl   b, %eax           tmp = b;  Atomic for aligned b

Twoje opcje to:

  • zrobić sklep sekwencyjno-konsystencyjny z xchg, np. mov $1, %eax / xchg %eax, a więc nie potrzebujesz oddzielnej bariery; to część sklepu. Myślę, że jest to najbardziej wydajna opcja na większości nowoczesnych sprzętów; kompilatory C++11 inne niż gcc używają xchg dla sklepów seq_cst.
  • Użyj mfence jako bariery. (GCC używa mov + mfence dla sklepów seq_cst).
  • Użyj lock addl $0, (%esp) jako bariery. Każda lockInstrukcja ed jest pełną barierą. czy lock xchg ma takie samo zachowanie jak mfence?

    (lub w inne miejsce, ale stos jest prawie zawsze prywatnie i gorąco w L1d, więc jest to dość dobry kandydat. Jednakże może to stworzyć łańcuch zależności dla czegoś, używając danych u dołu stosu.)

Możesz użyć xchg jako bariery tylko przez złożenie go do magazynu, ponieważ bezwarunkowo zapisuje lokalizację pamięci z wartością, która nie zależy od starej wartości.

Jeśli jest to możliwe, używanie xchg dla sklepu seq-cst jest prawdopodobnie najlepsze, mimo że czyta również ze współdzielonej lokalizacji. mfence czy na najnowszych procesorach Intela jest wolniej niż się spodziewano (czy ładuje i przechowuje tylko instrukcje, które są zmieniane?), blokuje również poza kolejnością wykonywanie niezależnych instrukcji Nie-pamięci tak samo jak lfence.

Może nawet warto użyć lock addl $0, (%esp)/(%rsp) zamiast mfence, nawet jeśli mfence jest dostępny, ale nie eksperymentowałem z wadami. Użycie -64(%rsp) lub czegoś może zmniejszyć prawdopodobieństwo wydłużenia zależności danych od czegoś gorącego (adresu lokalnego lub zwrotnego), ale to może sprawić, że narzędzia takie jak valgrind będą nieszczęśliwe.


lfence nigdy nie jest przydatny do zamawiania pamięci, chyba że czytasz z video RAM (lub innego słabo uporządkowanego regionu WC) z ładunkami MOVNTDQA.

Serializowanie out-of-order execution (ale nie bufor sklepu) nie jest przydatne, aby zatrzymać zmianę kolejności w magazynie (jedyny rodzaj, który silny model pamięci x86 pozwala na normalne regiony pamięci WB (write-back)).

Rzeczywiste przypadki użycia dla lfence służą do blokowania poza zleceniem wykonania rdtsc dla bardzo krótkich bloków kodu, lub dla łagodzenia widm poprzez blokowanie spekulacji poprzez oddział warunkowy lub pośredni.

Zobacz także Kiedy powinienem używać _mm_sfence _mm_lfence i _mm_mfence (moja odpowiedź i odpowiedź @BeeOnRope), aby dowiedzieć się więcej o tym, dlaczego lfence nie jest przydatne i kiedy należy używać każdej z instrukcji barierowych. (Lub w moim, c++ intrinsics przy programowaniu w C++ zamiast asm).

 7
Author: Peter Cordes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-20 22:32:20

Na marginesie innych odpowiedzi, Programiści HotSpot stwierdzili, że lock; addl $0,0(%%esp) z zerowym przesunięciem może nie być optymalny, na niektórych procesorach może wprowadzić fałszywe zależności danych ; powiązany błąd jdk.

Dotknięcie lokalizacji stosu z innym przesunięciem może poprawić wydajność w pewnych okolicznościach.

 6
Author: the8472,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 14:51:05

Ważną częścią lock; addl i xchgl jest prefiks lock. Jest niejawne dla xchgl. Naprawdę nie ma różnicy między tymi dwoma. Ja bym popatrzył jak się montują i wybrał ten, który jest krótszy (w bajtach), ponieważ zwykle jest szybszy dla równoważnych operacji na x86 (stąd triki takie jak xorl eax,eax)

Obecność SSE2 jest prawdopodobnie tylko proxy dla stanu rzeczywistego, który jest ostatecznie funkcją cpuid. Prawdopodobnie okazuje się, że SSE2 zakłada istnienie lfence i dostępność SSE2 była sprawdzana / buforowana podczas rozruchu. lfence jest wymagane, gdy jest dostępny.

 2
Author: Ben Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-20 19:31:06