Warum ist std :: atomic <T> :: is_lock_free () nicht so statisch wie constexpr?
9
Kann mir jemand sagen, ob std :: atomic :: is_lock_free () nicht so statisch wie constexpr ist? Es macht für mich keinen Sinn, es nicht statisch und / oder nicht kontextuell zu haben.
@MaxLanghof Meinst du, dass nicht alle Instanzen gleich ausgerichtet werden?
Neugieriger
1
Mike, nein, ich war mir nicht bewusst, aber danke für diesen Hinweis; es ist wirklich hilfreich für mich. Aber ich frage mich, warum es eine Entscheidung zwischen is_lock_free () und is_always_lock_free gibt. Es kann nicht an nicht ausgerichteten Atomics liegen, wie andere hier vorgeschlagen haben, da die Sprache nicht ausgerichtete Zugriffe definiert, um ohnehin undefiniertes Verhalten zu haben.
Alle Atomtypen mit Ausnahme von std :: atomic_flag können mithilfe von Mutexen oder anderen Sperroperationen implementiert werden, anstatt die Anweisungen für die sperrenfreie Atom-CPU zu verwenden. Atomtypen dürfen manchmal auch sperrenfrei sein, z. B. wenn nur ausgerichtete Speicherzugriffe auf einer bestimmten Architektur von Natur aus atomar sind, müssen falsch ausgerichtete Objekte desselben Typs Sperren verwenden.
Der C ++ - Standard empfiehlt (erfordert jedoch nicht), dass sperrfreie atomare Operationen auch adressfrei sind, dh für die Kommunikation zwischen Prozessen unter Verwendung des gemeinsam genutzten Speichers geeignet sind.
Wie von mehreren anderen erwähnt, std::is_always_lock_freekönnte es das sein, wonach Sie wirklich suchen.
Bearbeiten: Zur Verdeutlichung haben C ++ - Objekttypen einen Ausrichtungswert, der die Adressen ihrer Instanzen auf nur bestimmte Vielfache von Zweierpotenzen ( [basic.align]) beschränkt. Diese Ausrichtungswerte sind für grundlegende Typen implementierungsdefiniert und müssen nicht der Größe des Typs entsprechen. Sie können auch strenger sein als das, was die Hardware tatsächlich unterstützen könnte.
Beispielsweise unterstützt x86 (meistens) nicht ausgerichtete Zugriffe. Sie werden jedoch feststellen, dass die meisten Compiler alignof(double) == sizeof(double) == 8x86 verwenden, da nicht ausgerichtete Zugriffe eine Reihe von Nachteilen aufweisen (Geschwindigkeit, Caching, Atomizität ...). Aber zB #pragma pack(1) struct X { char a; double b; };oder alignas(1) double x;erlaubt es Ihnen, "nicht ausgerichtete" doubles zu haben. Wenn cppreference also von "ausgerichteten Speicherzugriffen" spricht, geschieht dies vermutlich im Hinblick auf die natürliche Ausrichtung des Typs für die Hardware, wobei ein C ++ - Typ nicht in einer Weise verwendet wird, die seinen Ausrichtungsanforderungen widerspricht (was UB wäre).
32-Bit x86 ist ein gutes Beispiel dafür, wo Sie ABIs finden alignof(double)==4. Hat aber std::atomic<double>immer noch alignof() = 8anstatt die Ausrichtung zur Laufzeit zu überprüfen. Die Verwendung einer gepackten Struktur, die atomar unterausrichtet, unterbricht den ABI und wird nicht unterstützt. (GCC für 32-Bit-x86 bevorzugt die natürliche Ausrichtung von 8-Byte-Objekten, aber Strukturpackregeln überschreiben diese und basieren nur auf alignof(T)z. B. i386 System V. G ++ hatte früher einen Fehler, bei dem atomic<int64_t>eine Struktur möglicherweise nicht atomar ist weil es gerade angenommen hat. GCC (für C nicht C ++) hat immer noch diesen Fehler!)
Peter Cordes
2
Eine korrekte Implementierung von C ++ 20 std::atomic_ref<double>lehnt jedoch entweder eine Unterausrichtung doublevollständig ab oder überprüft die Ausrichtung zur Laufzeit auf Plattformen, auf denen es legal ist, einfach doubleund int64_tweniger als natürlich ausgerichtet zu sein. (Weil atomic_ref<T>auf ein Objekt Talignof(T)
Peter Cordes
2
Siehe gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 für den jetzt behobenen libstdc ++ - Fehler und gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 für den immer noch defekten C-Fehler, einschließlich a reiner ISO C11-Testfall, der das Zerreißen eines _Atomic int64_tbeim Kompilieren mit Strom zeigt gcc -m32. Mein Punkt ist jedenfalls, dass echte Compiler keine unterausgerichteten Atomics unterstützen und (noch?) Keine Laufzeitprüfungen durchführen, #pragma packoder __attribute__((packed))nur zu Nicht-Atomizität führen. Objekte melden weiterhin, dass sie sind lock_free.
Peter Cordes
1
Aber ja, der Zweck von is_lock_free()ist es, Implementierungen zu ermöglichen , die anders funktionieren als aktuelle. mit Laufzeitprüfungen basierend auf der tatsächlichen Ausrichtung, um HW-unterstützte atomare Anweisungen zu verwenden oder eine Sperre zu verwenden.
is_lock_free hängt vom tatsächlichen System ab und kann zur Kompilierungszeit nicht ermittelt werden.
Relevante Erklärung:
Atomtypen dürfen manchmal auch sperrenfrei sein, z. B. wenn nur ausgerichtete Speicherzugriffe auf einer bestimmten Architektur von Natur aus atomar sind, müssen falsch ausgerichtete Objekte desselben Typs Sperren verwenden.
std::numeric_limits<int>::maxhängt von der Architektur ab, ist aber statisch und constexpr. Ich denke, es ist nichts falsch in der Antwort, aber ich kaufe nicht den ersten Teil der Argumentation
idclev 463035818
1
Definiert die Sprache nicht ausgerichteter Zugriffe ohnehin nicht als undefiniertes Verhalten, sodass eine Bewertung der Sperrfreiheit oder nicht zur Laufzeit Unsinn wäre?
Bonita Montero
1
Es ist nicht sinnvoll, zwischen ausgerichteten und nicht ausgerichteten Zugriffen zu entscheiden, da die Sprache letztere als undefiniertes Verhalten definiert.
Bonita Montero
@BonitaMontero Es gibt den Sinn "nicht ausgerichtet in der C ++ - Objektausrichtung" und "nicht ausgerichtet in dem, was die Hardware mag". Diese sind nicht unbedingt gleich, aber in der Praxis häufig. Das Beispiel, das Sie zeigen, ist ein solcher Fall, in dem der Compiler anscheinend die eingebaute Annahme hat, dass beide gleich sind - was nur bedeutet, dass dies für diesen Compileris_lock_free sinnlos ist .
Max Langhof
1
Sie können ziemlich sicher sein, dass ein Atom eine korrekte Ausrichtung hat, wenn eine Ausrichtungsanforderung besteht.
Bonita Montero
1
Ich habe Visual Studio 2019 auf meinem Windows-PC installiert und dieses Gerät hat auch einen ARMv8-Compiler. ARMv8 ermöglicht nicht ausgerichtete Zugriffe, aber Vergleiche und Swaps, gesperrte Adds usw. müssen ausgerichtet werden. Und auch reines Laden / reines Speichern unter Verwendung von ldpoder stp(Ladepaar oder Speicherpaar von 32-Bit-Registern) ist nur dann garantiert atomar, wenn sie auf natürliche Weise ausgerichtet sind.
Also habe ich ein kleines Programm geschrieben, um zu überprüfen, was is_lock_free () für einen beliebigen Atomzeiger zurückgibt. Also hier ist der Code:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Dies ist nur returns true, aka 1.
Diese Implementierung wählt die Verwendung, alignof( atomic<int64_t> ) == 8damit alle atomic<int64_t>korrekt ausgerichtet sind. Dies vermeidet die Notwendigkeit von Laufzeitausrichtungsprüfungen bei jedem Laden und Speichern.
(Anmerkung des Herausgebers: Dies ist üblich. Die meisten realen C ++ - Implementierungen funktionieren auf diese Weise. Aus diesem Grund std::is_always_lock_freeist dies so nützlich: Weil es normalerweise für Typen gilt, bei denen dies is_lock_free()jemals der Fall ist.)
Ja, die meisten Implementierungen geben an atomic<uint64_t>und alignof() == 8müssen daher die Ausrichtung zur Laufzeit nicht überprüfen. Diese alte API gibt ihnen die Möglichkeit, dies nicht zu tun, aber bei der aktuellen HW ist es viel sinnvoller, nur eine Ausrichtung zu verlangen (ansonsten UB, z. B. Nichtatomarität). Selbst in 32-Bit-Code, in dem int64_tmöglicherweise nur eine 4-Byte-Ausrichtung vorhanden ist, atomic<int64_t>sind 8 Byte erforderlich. Siehe meine Kommentare zu einer anderen Antwort
Peter Cordes
Mit anderen Worten ausgedrückt: Wenn ein Compiler den alignofWert für einen Grundtyp so festlegt wie die "gute" Ausrichtung der Hardware, ist diesis_lock_free immer der Fall true(und das wird auch so sein is_always_lock_free). Ihr Compiler hier macht genau das. Die API ist jedoch vorhanden, sodass andere Compiler andere Aufgaben ausführen können.
Max Langhof
1
Sie können ziemlich sicher sein, dass alle Atomics richtig ausgerichtet werden müssen, wenn die Sprache besagt, dass nicht ausgerichteter Zugriff ein undefiniertes Verhalten aufweist. Aus diesem Grund führt keine Implementierung Laufzeitprüfungen durch.
Bonita Montero
@BonitaMontero Ja, aber es gibt nichts in der Sprache, was dies verbietet alignof(std::atomic<double>) == 1(es würde also keinen "nicht ausgerichteten Zugriff" im C ++ - Sinne geben, daher kein UB), selbst wenn die Hardware nur sperrfreie atomare Operationen für doubles auf 4 oder 4 garantieren kann 8 Byte Grenzen. Der Compiler müsste dann in den nicht ausgerichteten Fällen Sperren verwenden (und is_lock_freeabhängig vom Speicherort der Objektinstanz den entsprechenden booleschen Wert zurückgeben ).
is_always_lock_free
?Antworten:
Wie auf cppreference erklärt :
Wie von mehreren anderen erwähnt,
std::is_always_lock_free
könnte es das sein, wonach Sie wirklich suchen.Bearbeiten: Zur Verdeutlichung haben C ++ - Objekttypen einen Ausrichtungswert, der die Adressen ihrer Instanzen auf nur bestimmte Vielfache von Zweierpotenzen (
[basic.align]
) beschränkt. Diese Ausrichtungswerte sind für grundlegende Typen implementierungsdefiniert und müssen nicht der Größe des Typs entsprechen. Sie können auch strenger sein als das, was die Hardware tatsächlich unterstützen könnte.Beispielsweise unterstützt x86 (meistens) nicht ausgerichtete Zugriffe. Sie werden jedoch feststellen, dass die meisten Compiler
alignof(double) == sizeof(double) == 8
x86 verwenden, da nicht ausgerichtete Zugriffe eine Reihe von Nachteilen aufweisen (Geschwindigkeit, Caching, Atomizität ...). Aber zB#pragma pack(1) struct X { char a; double b; };
oderalignas(1) double x;
erlaubt es Ihnen, "nicht ausgerichtete"double
s zu haben. Wenn cppreference also von "ausgerichteten Speicherzugriffen" spricht, geschieht dies vermutlich im Hinblick auf die natürliche Ausrichtung des Typs für die Hardware, wobei ein C ++ - Typ nicht in einer Weise verwendet wird, die seinen Ausrichtungsanforderungen widerspricht (was UB wäre).Hier finden Sie weitere Informationen: Welche tatsächlichen Auswirkungen haben erfolgreiche nicht ausgerichtete Zugriffe auf x86?
Bitte lesen Sie auch die aufschlussreichen Kommentare von @Peter Cordes unten!
quelle
alignof(double)==4
. Hat aberstd::atomic<double>
immer nochalignof() = 8
anstatt die Ausrichtung zur Laufzeit zu überprüfen. Die Verwendung einer gepackten Struktur, die atomar unterausrichtet, unterbricht den ABI und wird nicht unterstützt. (GCC für 32-Bit-x86 bevorzugt die natürliche Ausrichtung von 8-Byte-Objekten, aber Strukturpackregeln überschreiben diese und basieren nur aufalignof(T)
z. B. i386 System V. G ++ hatte früher einen Fehler, bei dematomic<int64_t>
eine Struktur möglicherweise nicht atomar ist weil es gerade angenommen hat. GCC (für C nicht C ++) hat immer noch diesen Fehler!)std::atomic_ref<double>
lehnt jedoch entweder eine Unterausrichtungdouble
vollständig ab oder überprüft die Ausrichtung zur Laufzeit auf Plattformen, auf denen es legal ist, einfachdouble
undint64_t
weniger als natürlich ausgerichtet zu sein. (Weilatomic_ref<T>
auf ein ObjektT
alignof(T)
_Atomic int64_t
beim Kompilieren mit Strom zeigtgcc -m32
. Mein Punkt ist jedenfalls, dass echte Compiler keine unterausgerichteten Atomics unterstützen und (noch?) Keine Laufzeitprüfungen durchführen,#pragma pack
oder__attribute__((packed))
nur zu Nicht-Atomizität führen. Objekte melden weiterhin, dass sie sindlock_free
.is_lock_free()
ist es, Implementierungen zu ermöglichen , die anders funktionieren als aktuelle. mit Laufzeitprüfungen basierend auf der tatsächlichen Ausrichtung, um HW-unterstützte atomare Anweisungen zu verwenden oder eine Sperre zu verwenden.Sie können verwenden
std::is_always_lock_free
is_lock_free
hängt vom tatsächlichen System ab und kann zur Kompilierungszeit nicht ermittelt werden.Relevante Erklärung:
quelle
std::numeric_limits<int>::max
hängt von der Architektur ab, ist aber statisch undconstexpr
. Ich denke, es ist nichts falsch in der Antwort, aber ich kaufe nicht den ersten Teil der Argumentationis_lock_free
sinnlos ist .Ich habe Visual Studio 2019 auf meinem Windows-PC installiert und dieses Gerät hat auch einen ARMv8-Compiler. ARMv8 ermöglicht nicht ausgerichtete Zugriffe, aber Vergleiche und Swaps, gesperrte Adds usw. müssen ausgerichtet werden. Und auch reines Laden / reines Speichern unter Verwendung von
ldp
oderstp
(Ladepaar oder Speicherpaar von 32-Bit-Registern) ist nur dann garantiert atomar, wenn sie auf natürliche Weise ausgerichtet sind.Also habe ich ein kleines Programm geschrieben, um zu überprüfen, was is_lock_free () für einen beliebigen Atomzeiger zurückgibt. Also hier ist der Code:
Und dies ist die Demontage von isLockFreeAtomic
Dies ist nur
returns true
, aka1
.Diese Implementierung wählt die Verwendung,
alignof( atomic<int64_t> ) == 8
damit alleatomic<int64_t>
korrekt ausgerichtet sind. Dies vermeidet die Notwendigkeit von Laufzeitausrichtungsprüfungen bei jedem Laden und Speichern.(Anmerkung des Herausgebers: Dies ist üblich. Die meisten realen C ++ - Implementierungen funktionieren auf diese Weise. Aus diesem Grund
std::is_always_lock_free
ist dies so nützlich: Weil es normalerweise für Typen gilt, bei denen diesis_lock_free()
jemals der Fall ist.)quelle
atomic<uint64_t>
undalignof() == 8
müssen daher die Ausrichtung zur Laufzeit nicht überprüfen. Diese alte API gibt ihnen die Möglichkeit, dies nicht zu tun, aber bei der aktuellen HW ist es viel sinnvoller, nur eine Ausrichtung zu verlangen (ansonsten UB, z. B. Nichtatomarität). Selbst in 32-Bit-Code, in demint64_t
möglicherweise nur eine 4-Byte-Ausrichtung vorhanden ist,atomic<int64_t>
sind 8 Byte erforderlich. Siehe meine Kommentare zu einer anderen Antwortalignof
Wert für einen Grundtyp so festlegt wie die "gute" Ausrichtung der Hardware, ist diesis_lock_free
immer der Falltrue
(und das wird auch so seinis_always_lock_free
). Ihr Compiler hier macht genau das. Die API ist jedoch vorhanden, sodass andere Compiler andere Aufgaben ausführen können.alignof(std::atomic<double>) == 1
(es würde also keinen "nicht ausgerichteten Zugriff" im C ++ - Sinne geben, daher kein UB), selbst wenn die Hardware nur sperrfreie atomare Operationen fürdouble
s auf 4 oder 4 garantieren kann 8 Byte Grenzen. Der Compiler müsste dann in den nicht ausgerichteten Fällen Sperren verwenden (undis_lock_free
abhängig vom Speicherort der Objektinstanz den entsprechenden booleschen Wert zurückgeben ).