Czy wywołanie funkcji jest skuteczną barierą pamięci dla nowoczesnych platform?

W bazie kodowej, którą przejrzałem, znalazłem następujący idiom.

void notify(struct actor_t act) {
    write(act.pipe, "M", 1);
}
// thread A sending data to thread B
void send(byte *data) {
    global.data = data;
    notify(threadB);
}
// in thread B event loop
read(this.sock, &cmd, 1);
switch (cmd) {
    case 'M': use_data(global.data);break;
    ...
}

"trzymaj", powiedziałem do autora, starszego członka mojego zespołu, " nie ma tu bariery pamięci! Nie gwarantujesz, że global.data zostanie przepłukana z pamięci podręcznej do pamięci głównej. Jeśli wątek a i wątek B będą działać w dwóch różnych procesorach - ten schemat może się nie udać".

[3]}starszy programista uśmiechnął się i wyjaśnił powoli, jakby wyjaśniał swojemu pięcioletniemu chłopcu, jak zawiązać sznurowadła: "słuchaj Młody chłopcze, mamy widziałem tutaj wiele błędów związanych z wątkiem, w testach wysokiego obciążenia i w prawdziwych klientach", przerwał, aby podrapać swoją długą brodę, "ale nigdy nie mieliśmy błędu z tym idiomem".

"ale, to jest napisane w książce..."

"cisza!- może teoretycznie nie jest to gwarantowane ,ale w praktyce to, że użyłeś wywołania funkcji, jest faktycznie barierą pamięci. Kompilator nie zmieni kolejności instrukcji global.data = data, ponieważ nie może wiedzieć, czy ktoś używa jej w wywołaniu funkcji, a Architektura x86 zapewni, że pozostałe Procesory zobaczą ten fragment danych globalnych w czasie, gdy wątek B odczyta polecenie z potoku. Zapewniamy, że mamy wiele problemów z prawdziwego świata, którymi musimy się martwić. Nie musimy inwestować dodatkowego wysiłku w fałszywe problemy teoretyczne.

"Bądź pewny, mój chłopcze, z czasem zrozumiesz, jak oddzielić prawdziwy problem Od Nie-problemów, które muszę uzyskać."

Czy on ma rację? Czy to naprawdę nie problem w praktyce (powiedzmy x86, x64 i Ramię)? To wbrew wszystkiemu, czego się nauczyłem, ale on ma długą brodę i naprawdę inteligentny wygląd! Dodatkowe punkty, jeśli pokażesz mi kawałek kodu udowadniający, że się myli!
Author: Craig McQueen, 2012-05-22

4 answers

Bariery pamięci nie są po to, aby zapobiec zmianie kolejności instrukcji. Nawet jeśli instrukcje nie są zmieniane, może to nadal powodować problemy ze spójnością pamięci podręcznej. Co do zmiany kolejności-to zależy od Twojego kompilatora i ustawień. ICC jest szczególnie agresywny przy zmianie kolejności. MSVC z optymalizacją całego programu też może być.

Jeśli współdzielona zmienna danych jest zadeklarowana jako volatile, nawet jeśli nie ma go w specyfikacji, większość kompilatorów wygeneruje zmienną pamięci wokół odczytu i zapisu od zmiennej i zapobiec ponownemu uporządkowaniu. nie jest to poprawny sposób użycia volatile, ani do czego to było przeznaczone.

(gdybym miał jeszcze jakieś głosy, dałbym +1 twoje pytanie do narracji.)

 10
Author: Mahmoud Al-Qudsi,
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
2012-05-22 08:21:34

W praktyce wywołanie funkcji jest barierą kompilatora, co oznacza, że kompilator nie przesunie globalnego dostępu do pamięci poza wywołanie. Zastrzeżeniem do tego są funkcje, o których kompilator coś wie, np. wbudowane, inlined functions(należy pamiętać!) itp.

Więc bariera pamięci procesora (oprócz bariery kompilatora) jest teoretycznie potrzebna, aby to zadziałało. Jednak, ponieważ nazywasz odczyt i zapis, które są syscalls, które zmieniają stan globalny, jestem dość pewnie, że gdzieś w implementacji jądra pojawiają się bariery pamięci. Nie ma jednak takiej gwarancji, więc teoretycznie potrzebne są bariery.

 8
Author: janneb,
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
2012-05-22 08:24:32

Podstawowa zasada brzmi: kompilator musi sprawić, że stan globalny będzie wyglądał dokładnie tak, jak go zakodowałeś, ale jeśli udowodni, że dana funkcja nie używa zmiennych globalnych, to może zaimplementować algorytm w dowolny sposób.

Wynik jest taki, że tradycyjne Kompilatory zawsze traktowały funkcje w innej jednostce kompilacji jako barierę pamięci, ponieważ nie mogły widzieć wewnątrz tych funkcji. Coraz częściej nowoczesne Kompilatory rozwijają "cały program" lub " link strategie optymalizacji czasu, które przełamują te bariery i spowodują, że źle napisany kod zawodzi, mimo że działa dobrze od lat.

Jeśli dana funkcja jest w bibliotece współdzielonej, to nie będzie w stanie zobaczyć jej wnętrza, ale Jeśli funkcja jest zdefiniowana przez standard C, to nie musi - już wie, co robi funkcja - więc musisz być ostrożny również z nimi. Zauważ, że kompilator nie rozpozna jądra wywołanie tego, co to jest, ale sam akt Wstawienia czegoś, czego kompilator nie może rozpoznać (wbudowany asembler lub wywołanie funkcji do pliku asemblera) stworzy barierę pamięci samą w sobie.

W Twoim przypadku notify będzie albo czarną skrzynką, której kompilator nie widzi (funkcja biblioteczna), albo będzie zawierać rozpoznawalną barierę pamięci, więc najprawdopodobniej będziesz bezpieczny.

W praktyce, trzeba napisać Bardzo zły kod, aby się nad tym zapaść.

 2
Author: ams,
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
2012-05-22 09:34:07

W praktyce ma rację i w tym konkretnym przypadku implikuje się barierę pamięci.

Ale chodzi o to, że jeśli jego obecność jest "dyskusyjna", kod jest już zbyt złożony i niejasny.

Naprawdę ludzie, użyj mutex lub innych właściwych konstrukcji. Jest to jedyny bezpieczny sposób na radzenie sobie z wątkami i pisanie łatwego do utrzymania kodu.

I może zobaczysz inne błędy, takie jak, że kod jest nieprzewidywalny, jeśli send() jest wywoływane więcej niż jeden raz.

 1
Author: amadvance,
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
2012-05-28 16:32:12