Dlaczego std :: atomic <T> :: is_lock_free () nie jest statyczny tak samo jak constexpr?
9
Czy ktoś może mi powiedzieć, czy std :: atomic :: is_lock_free () nie jest statyczny tak dobrze jak constexpr? Posiadanie go jako niestatycznego i / lub jako non-constexpr nie ma dla mnie sensu.
@MaxLanghof Czy masz na myśli, że nie wszystkie wystąpienia zostaną wyrównane w ten sam sposób?
ciekawy,
1
Mike, nie, nie wiedziałem, ale dzięki za podpowiedź; to jest dla mnie bardzo pomocne. Ale zadaję sobie pytanie, dlaczego istnieje decyzja między is_lock_free () a is_always_lock_free. Nie może być tak z powodu nieprzystosowanej atomiki, jak sugerują tu inni, ponieważ język definiuje nieprzystosowane dostępy tak, aby miały nieokreślone zachowanie.
Wszystkie typy atomowe z wyjątkiem std :: atomic_flag mogą być implementowane przy użyciu muteksów lub innych operacji blokowania, zamiast instrukcji atomowych bez blokowania instrukcji. Typy atomowe mogą być czasami wolne od blokowania, np. Jeśli tylko wyrównane dostępy do pamięci są naturalnie atomowe w danej architekturze, niedopasowane obiekty tego samego typu muszą używać blokad.
Standard C ++ zaleca (ale nie wymaga), aby operacje atomowe bez blokowania były również pozbawione adresu, czyli odpowiednie do komunikacji między procesami wykorzystującymi pamięć współdzieloną.
Jak wspomniano przez wiele innych, std::is_always_lock_freemoże być tym, czego naprawdę szukasz.
Edycja: Aby wyjaśnić, typy obiektów C ++ mają wartość wyrównania, która ogranicza adresy ich instancji do tylko niektórych wielokrotności potęg dwóch ( [basic.align]). Te wartości wyrównania są zdefiniowane w implementacji dla typów podstawowych i nie muszą być równe wielkości typu. Mogą być również bardziej rygorystyczne niż to, co sprzęt może faktycznie obsługiwać.
Na przykład x86 (głównie) obsługuje niezrównane dostępy. Znajdziesz jednak większość kompilatorów mających alignof(double) == sizeof(double) == 8x86, ponieważ nieprzypisane dostępy mają wiele wad (szybkość, buforowanie, atomowość ...). Ale np #pragma pack(1) struct X { char a; double b; };lub alignas(1) double x;pozwala mieć „niewyrównany” doubles. Kiedy więc cppreference mówi o „wyrównanym dostępie do pamięci”, przypuszczalnie robi to w kategoriach naturalnego wyrównania typu dla sprzętu, nie używając typu C ++ w sposób sprzeczny z jego wymaganiami dotyczącymi wyrównania (którym byłby UB).
32-bitowy x86 jest dobrym przykładem tego, gdzie można znaleźć ABI alignof(double)==4. Ale std::atomic<double>nadal ma alignof() = 8zamiast sprawdzania wyrównania w czasie wykonywania. Użycie upakowanej struktury, która nie wyrównuje atomów, psuje ABI i nie jest obsługiwane. (GCC dla 32-bitowego x86 woli nadawać obiektom 8-bajtowym naturalne wyrównanie, ale reguły pakowania struktur zastępują to i są oparte tylko na alignof(T), np. Na i386 System V. G ++ miał błąd, w którym atomic<int64_t>wewnątrz struktury może nie być atomowy bo to tylko założenie. GCC (dla C nie C ++) wciąż ma ten błąd!)
Peter Cordes
2
Ale prawidłowa implementacja C ++ 20 std::atomic_ref<double>albo doublecałkowicie odrzuci niedostosowanie , albo sprawdzi wyrównanie w środowisku wykonawczym na platformach, na których jest to zgodne z prawem doublei int64_tjest mniej niż naturalnie wyrównane. (Ponieważ atomic_ref<T>działa na obiekcie, który został zadeklarowany jako zwykły Ti ma tylko minimalne wyrównanie alignof(T)bez możliwości nadania mu dodatkowego wyrównania.)
Peter Cordes,
2
Zobacz gcc.gnu.org/bugzilla/show_bug.cgi?id=62259, aby dowiedzieć się o poprawionym teraz błędzie libstdc ++, a gcc.gnu.org/bugzilla/show_bug.cgi?id=65146, aby dowiedzieć się o wciąż uszkodzonym błędzie C, w tym czysta walizka testowa ISO C11, która pokazuje łzawienie _Atomic int64_tpo skompilowaniu z prądem gcc -m32. W każdym razie, chodzi mi o to, że realne kompilatory nie obsługują niedostatecznie wyrównanych Atomics i nie robić kontrole uruchomieniowe (jeszcze?), Więc #pragma packalbo __attribute__((packed))będzie tylko prowadzić do nieprzestrzegania atomowości; obiekty nadal zgłaszają, że są lock_free.
Peter Cordes,
1
Ale tak, celem is_lock_free()jest umożliwienie implementacjom działania innego niż w rzeczywistości. z kontrolami wykonawczymi opartymi na faktycznym wyrównaniu w celu użycia instrukcji atomowych obsługiwanych przez HW lub blokady.
is_lock_free zależy od rzeczywistego systemu i nie można go ustalić w czasie kompilacji.
Odpowiednie wyjaśnienie:
Typy atomowe mogą być czasami wolne od blokowania, np. Jeśli tylko wyrównane dostępy do pamięci są naturalnie atomowe w danej architekturze, niedopasowane obiekty tego samego typu muszą używać blokad.
std::numeric_limits<int>::maxzależy od architektury, ale jest statyczny i constexpr.
Wydaje
1
Czy i tak nie definiuje dostępu do języka, który ma nieokreślony charakter, i że ma nieokreślone zachowanie, więc ocena braku blokady w czasie wykonywania byłaby nonsensem?
Bonita Montero,
1
Nie ma sensu decydować między dostępami wyrównanymi i nieprzystosowanymi, ponieważ język definiuje to drugie jako niezdefiniowane zachowanie.
Bonita Montero,
@BonitaMontero Występuje „nieprzystosowane w sensie wyrównania obiektów w C ++” i „nieprzystosowane w sensie tego, co lubi sprzęt”. Niekoniecznie są one takie same, ale w praktyce często są. Pokazany przykład jest jednym z takich przypadków, w których kompilator najwyraźniej ma wbudowane założenie, że oba są takie same - co oznacza tylko, że nie is_lock_freema sensu w tym kompilatorze .
Max Langhof,
1
Możesz być całkiem pewien, że atom będzie miał odpowiednie wyrównanie, jeśli istnieje wymóg wyrównania.
Bonita Montero,
1
Mam zainstalowany program Visual Studio 2019 na moim komputerze z systemem Windows, a to devenv ma również kompilator ARMv8. ARMv8 pozwala na niezrównany dostęp, ale porównywanie i zamiana, zablokowane dodawanie itp. Są obowiązkowe do wyrównania. A także czyste ładowanie / przechowywanie za pomocą ldplub stp(para ładująca lub para 32-bitowych rejestrów) gwarantuje, że będą atomowe tylko wtedy, gdy są naturalnie wyrównane.
Napisałem więc mały program, aby sprawdzić, co is_lock_free () zwraca dla dowolnego wskaźnika atomowego. Oto kod:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
To tylko returns trueaka 1.
Ta implementacja decyduje się na użycie, alignof( atomic<int64_t> ) == 8więc każda atomic<int64_t>jest poprawnie wyrównana. Pozwala to uniknąć konieczności sprawdzania wyrównania środowiska wykonawczego dla każdego ładunku i magazynu.
(Uwaga redaktora: jest to powszechne; większość rzeczywistych implementacji C ++ działa w ten sposób. Dlatego std::is_always_lock_freejest tak przydatna: ponieważ zazwyczaj jest prawdziwa dla typów, w których is_lock_free()zawsze jest prawdziwa.)
Tak, większość implementacji wybrać dać atomic<uint64_t>, a alignof() == 8więc nie trzeba sprawdzić ustawienie na starcie. Ten stary interfejs API daje im opcję, aby tego nie robić, ale na obecnym sprzęcie komputerowym sensowniej jest po prostu wymagać wyrównania (w przeciwnym razie UB, np. Brak atomowości). Nawet w 32-bitowym kodzie, w którym int64_twyrównanie tylko 4-bajtowe atomic<int64_t>wymaga 8-bajtowego wyrównania . Zobacz moje komentarze do innej odpowiedzi
Peter Cordes,
Innymi słowy: jeśli kompilator zdecyduje się uczynić alignofwartość dla typu podstawowego taką samą jak „dobre” wyrównanie sprzętu, tois_lock_free zawsze będzie true(i tak też będzie is_always_lock_free). Twój kompilator tutaj właśnie to robi. Ale interfejs API istnieje, więc inne kompilatory mogą robić różne rzeczy.
Max Langhof,
1
Możesz być całkiem pewien, że jeśli język mówi, że nieprzypisany dostęp ma nieokreślone zachowanie, wszystkie atomiki muszą być odpowiednio wyrównane. Z tego powodu żadna implementacja nie wykona żadnych kontroli środowiska wykonawczego.
Bonita Montero,
@BonitaMontero Tak, ale w języku nie ma nic, co by tego zabraniało alignof(std::atomic<double>) == 1(więc nie byłoby „niezaangażowanego dostępu” w sensie C ++, a więc bez UB), nawet jeśli sprzęt może zagwarantować jedynie blokadowe operacje atomowe dla doubles na 4 lub 8 bajtów granic. Kompilator musiałby wówczas użyć blokad w niezrównanych przypadkach (i zwrócić odpowiednią wartość logiczną z is_lock_free, w zależności od lokalizacji pamięci wystąpienia obiektu).
is_always_lock_free
?Odpowiedzi:
Jak wyjaśniono na cppreference :
Jak wspomniano przez wiele innych,
std::is_always_lock_free
może być tym, czego naprawdę szukasz.Edycja: Aby wyjaśnić, typy obiektów C ++ mają wartość wyrównania, która ogranicza adresy ich instancji do tylko niektórych wielokrotności potęg dwóch (
[basic.align]
). Te wartości wyrównania są zdefiniowane w implementacji dla typów podstawowych i nie muszą być równe wielkości typu. Mogą być również bardziej rygorystyczne niż to, co sprzęt może faktycznie obsługiwać.Na przykład x86 (głównie) obsługuje niezrównane dostępy. Znajdziesz jednak większość kompilatorów mających
alignof(double) == sizeof(double) == 8
x86, ponieważ nieprzypisane dostępy mają wiele wad (szybkość, buforowanie, atomowość ...). Ale np#pragma pack(1) struct X { char a; double b; };
lubalignas(1) double x;
pozwala mieć „niewyrównany”double
s. Kiedy więc cppreference mówi o „wyrównanym dostępie do pamięci”, przypuszczalnie robi to w kategoriach naturalnego wyrównania typu dla sprzętu, nie używając typu C ++ w sposób sprzeczny z jego wymaganiami dotyczącymi wyrównania (którym byłby UB).Oto więcej informacji: Jaki jest rzeczywisty wpływ udanego niezaangażowanego dostępu na x86?
Sprawdź także wnikliwe komentarze @Peter Cordes poniżej!
źródło
alignof(double)==4
. Alestd::atomic<double>
nadal maalignof() = 8
zamiast sprawdzania wyrównania w czasie wykonywania. Użycie upakowanej struktury, która nie wyrównuje atomów, psuje ABI i nie jest obsługiwane. (GCC dla 32-bitowego x86 woli nadawać obiektom 8-bajtowym naturalne wyrównanie, ale reguły pakowania struktur zastępują to i są oparte tylko naalignof(T)
, np. Na i386 System V. G ++ miał błąd, w którymatomic<int64_t>
wewnątrz struktury może nie być atomowy bo to tylko założenie. GCC (dla C nie C ++) wciąż ma ten błąd!)std::atomic_ref<double>
albodouble
całkowicie odrzuci niedostosowanie , albo sprawdzi wyrównanie w środowisku wykonawczym na platformach, na których jest to zgodne z prawemdouble
iint64_t
jest mniej niż naturalnie wyrównane. (Ponieważatomic_ref<T>
działa na obiekcie, który został zadeklarowany jako zwykłyT
i ma tylko minimalne wyrównaniealignof(T)
bez możliwości nadania mu dodatkowego wyrównania.)_Atomic int64_t
po skompilowaniu z prądemgcc -m32
. W każdym razie, chodzi mi o to, że realne kompilatory nie obsługują niedostatecznie wyrównanych Atomics i nie robić kontrole uruchomieniowe (jeszcze?), Więc#pragma pack
albo__attribute__((packed))
będzie tylko prowadzić do nieprzestrzegania atomowości; obiekty nadal zgłaszają, że sąlock_free
.is_lock_free()
jest umożliwienie implementacjom działania innego niż w rzeczywistości. z kontrolami wykonawczymi opartymi na faktycznym wyrównaniu w celu użycia instrukcji atomowych obsługiwanych przez HW lub blokady.Możesz użyć
std::is_always_lock_free
is_lock_free
zależy od rzeczywistego systemu i nie można go ustalić w czasie kompilacji.Odpowiednie wyjaśnienie:
źródło
std::numeric_limits<int>::max
zależy od architektury, ale jest statyczny iconstexpr
.is_lock_free
ma sensu w tym kompilatorze .Mam zainstalowany program Visual Studio 2019 na moim komputerze z systemem Windows, a to devenv ma również kompilator ARMv8. ARMv8 pozwala na niezrównany dostęp, ale porównywanie i zamiana, zablokowane dodawanie itp. Są obowiązkowe do wyrównania. A także czyste ładowanie / przechowywanie za pomocą
ldp
lubstp
(para ładująca lub para 32-bitowych rejestrów) gwarantuje, że będą atomowe tylko wtedy, gdy są naturalnie wyrównane.Napisałem więc mały program, aby sprawdzić, co is_lock_free () zwraca dla dowolnego wskaźnika atomowego. Oto kod:
I to jest demontaż isLockFreeAtomic
To tylko
returns true
aka1
.Ta implementacja decyduje się na użycie,
alignof( atomic<int64_t> ) == 8
więc każdaatomic<int64_t>
jest poprawnie wyrównana. Pozwala to uniknąć konieczności sprawdzania wyrównania środowiska wykonawczego dla każdego ładunku i magazynu.(Uwaga redaktora: jest to powszechne; większość rzeczywistych implementacji C ++ działa w ten sposób. Dlatego
std::is_always_lock_free
jest tak przydatna: ponieważ zazwyczaj jest prawdziwa dla typów, w którychis_lock_free()
zawsze jest prawdziwa.)źródło
atomic<uint64_t>
, aalignof() == 8
więc nie trzeba sprawdzić ustawienie na starcie. Ten stary interfejs API daje im opcję, aby tego nie robić, ale na obecnym sprzęcie komputerowym sensowniej jest po prostu wymagać wyrównania (w przeciwnym razie UB, np. Brak atomowości). Nawet w 32-bitowym kodzie, w którymint64_t
wyrównanie tylko 4-bajtoweatomic<int64_t>
wymaga 8-bajtowego wyrównania . Zobacz moje komentarze do innej odpowiedzialignof
wartość dla typu podstawowego taką samą jak „dobre” wyrównanie sprzętu, tois_lock_free
zawsze będzietrue
(i tak też będzieis_always_lock_free
). Twój kompilator tutaj właśnie to robi. Ale interfejs API istnieje, więc inne kompilatory mogą robić różne rzeczy.alignof(std::atomic<double>) == 1
(więc nie byłoby „niezaangażowanego dostępu” w sensie C ++, a więc bez UB), nawet jeśli sprzęt może zagwarantować jedynie blokadowe operacje atomowe dladouble
s na 4 lub 8 bajtów granic. Kompilator musiałby wówczas użyć blokad w niezrównanych przypadkach (i zwrócić odpowiednią wartość logiczną zis_lock_free
, w zależności od lokalizacji pamięci wystąpienia obiektu).