std::atomic
-
Ich durchforste gerade den aktuellen C++ Entwurf und stoße in der <atomic> Dokumentation auf einige Fragen:
1. Wie spielen die Typen:
struct atomic_flag; struct atomic_bool; struct atomic_char; struct atomic_schar; struct atomic_uchar; struct atomic_short; struct atomic_ushort; struct atomic_int; struct atomic_uint; struct atomic_long; struct atomic_ulong; struct atomic_llong; struct atomic_ullong; struct atomic_wchar_t; struct atomic_address;
mit dem Template
template<typename T> struct atomic;
zusammen?
2. Wo ist der Unterschied zwischen
void atomic_thread_fence(memory_order order); void atomic_signal_fence(memory_order order);
Ins besondere atomic_signal_fence verstehe ich nicht. Die Dokumentation sagt:
equivalent to atomic_thread_fence(order), except that synchronizes with relationships are established only between a thread and a signal handler executed in the same thread.
Aber was für ein "signal handler" ist hier gemeint?
3. Warum erwarten viele Methoden std::memory_ordner als Parameter? Wäre es nicht viel sinnvoller diese als Template-Argument zu übergeben?
Ich habe schon eine Weile gesucht und nichts ausreichendes für mich gefunden. Kann man irgendwo vlt. schon eine Implementation bestaunen?
-
Hallo,
zu 1: dies sind die Basistypen für die Spezialisierungen der einzelnen atomic<T>-Klassen
zu 2 und 3: weiß ich leider nicht
zur letzten Frage: s. http://www.c-plusplus.net/forum/p2002687#2002687
-
Aber was für ein "signal handler" ist hier gemeint?
Na signal handler halt. Das was kaum jemand verwendet
http://www.cplusplus.com/reference/clibrary/csignal/D.h. es handelt sich bloss um einen fence für den Compiler selbst, nicht aber für die CPU. Sozusagen.
Warum erwarten viele Methoden std::memory_ordner als Parameter?
SEHR gute Frage. Keine Ahnung. Spontan würde ich auch sagen Template-Parameter wäre besser.
Andrerseits werden die meisten Implementierungen vermutlich vom Compiler-Hersteller kommen, und der kann entsprechende Optimierungen in den Compiler einbauen. D.h. wenn der Parameter "compiletime-constant" ist gibt es keinen absoluten Grund, dass dabei weniger performanter Code erzeugt werden muss, als wenn es ein Template-Parameter wäre.
-
Zu den generischen bzw. built-in-type Varianten der atomic Klasse: Ich habe das bereits der Dokumentation entnommen, kann den Sinn jedoch nicht ganz nachvollziehen. Wie wird das unterschieden, ob address, integral, etc? Per type_trait?
Wo wir beim Thema sind: Kennt jemand die exakte Verwendung von is_lock_free()?
hustbaer schrieb:
Aber was für ein "signal handler" ist hier gemeint?
Na signal handler halt. Das was kaum jemand verwendet
Das hatte ich vollkommen verdrängt. Habe ich noch nie in "echtem" Code gesehen
Soll im Endeffekt heißen, dass nur die atomic_thread_fence von Bedeutung ist.
So wie ich das verstanden habe führt dass einfach die entsprechende Serialisierungs Instruktion, bestenfalls als Compiler-Intrinsic, aus. Kann das jemand bestätigen dementieren?
Dabei würde mich auch interessieren was genau mit std::memory_order_consume gemeint sein soll. Letztlich kenne ich unter Intel 64 / IA-32 nur 3 verschiedene FENCE Instruktionen (MFENCE, SFENCE, LFENCE).hustbaer schrieb:
SEHR gute Frage. Keine Ahnung. Spontan würde ich auch sagen Template-Parameter wäre besser.
Ein normaler Parameter macht für mich nur dann Sinn, wenn es sich um einen variablen Ausdruck handelt, der zur Kompilier-Zeit nicht bekannt sein muss. Da aber gerade diese Inter-Process Implementation genaustes wissen über das was man tut erfordern, sehe ich keinen Verwendungszweck den man nicht auch per Template lösen könnte. Vor allem, wie soll die Implementation aussehen? Scheint wohl wirklich auf Optimierungen hinauszulaufen.
Ehrlich halte ich auch die den Methoden entsprechenden Funktionen für relativ unnötig ...
-
FrEEzE2046 schrieb:
3. Warum erwarten viele Methoden std::memory_ordner als Parameter? Wäre es nicht viel sinnvoller diese als Template-Argument zu übergeben?
Jein. Ist eine philosophische Frage. Beim direkten verwenden von std::atomic ist ein template Param wahrscheinlich besser. Aber wenn es ein paar Abstraktionsschichten gibt, dann wird der parameter memory_order praktisch, da man ihn einfacher Speichern und weitergeben kann. Template Parameter würden das deutlich verkomplizieren.
Weiters ist memory_order ja eine Konstante und da die atomic Sachen sowieso inline sind (weil template) kann der Compiler enorm einfach optimieren. Und das ist, soweit ich mich erinnere der Grund gewesen warum es nie weiter diskutiert wurde. Weil performance identisch sein sollte.
zu 2:
Single Threaded Umgebung, aber mit signalen. Ein signal kann ja aus einem single threaded Programm ein nebenläufiges machen. uU gibt es hier spezielle optimierungen.Weiters ist der signal_fence nur ein fence für den aktuellen Thread + seine Signal Handler. Ein weiterer Thread würde über den fence drüber laufen soweit ich das verstanden habe.
zu lock_free: Was ist die Frage? Das gibt eben an ob du dieses atomic Objekt lock free verwenden kannst (weil es zB CAS kann).
-
Shade Of Mine schrieb:
zu lock_free: Was ist die Frage? Das gibt eben an ob du dieses atomic Objekt lock free verwenden kannst (weil es zB CAS kann).
Ich nehme an du meinst lock_free gibt an dass die atomare Operation selbst lock free implementiert ist.
"Lock free verwenden" macht für mich keinen Sinn, was soll das bedeuten?
-
hustbaer schrieb:
Shade Of Mine schrieb:
zu lock_free: Was ist die Frage? Das gibt eben an ob du dieses atomic Objekt lock free verwenden kannst (weil es zB CAS kann).
Ich nehme an du meinst lock_free gibt an dass die atomare Operation selbst lock free implementiert ist.
"Lock free verwenden" macht für mich keinen Sinn, was soll das bedeuten?
Ja, schlecht formuliert. Ich meinte in einem "lock freien kontext verwenden". Es gibt ja zB lock freie datenstrukturen und um diese zu implementieren braucht man natürlich auch lock freie operationen. Weil sonst nehm ich einfach eine lockende datenstruktur und spar mir die komplexe implementierung (da ich ja so oder so locken muss).
-
Wie werden die memory_order abhängigen Funktionen überhaupt implementiert? Da bleiben ja nur noch Compiler intrinsics um einigermaßen performant zu bleiben.
Ich bin immer noch auf der Suche was einige der memory_order's für Auswirkungen haben. Ich habe in einem älteren Thread gelesen, dass du ebenfalls eine Implementierung von atomic<T> vorgenommen hast ... wie genau hast du die memory_order unterschieden?
-
Erstmal kleiner Disclaimer: ich kenne mich auf dem Gebiet nicht wirklich gut aus. Ich habe mich mal reingelesen gehabt, aber multithreading ist ja so schon enorm komplex und auf dieser Ebene ist es einfach nur wahnsinn.
FrEEzE2046 schrieb:
Wie werden die memory_order abhängigen Funktionen überhaupt implementiert? Da bleiben ja nur noch Compiler intrinsics um einigermaßen performant zu bleiben.
Es ist eine sehr haarige Sache, keine Frage.
Irgendwer hat das mal verlinkt: http://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/a00755_source.html
Wunderschönes Beispiel wie komplex das ganze Thema ist.Es ist im Prinzip nicht eine Frage des Compilers sondern eher der Architektur. Die meisten Prozessor können ja out-of-order execution, was aber nichts anderes ist als ein instruction reordering. In einem single Thread ist das ja kein Problem da der Prozessor sieht welcher Befehl von welchem abhängt. Wenn jetzt aber mehrere Thread ins Spiel kommen ist das nicht mehr ersichtlich und man braucht fences (in gewissen Situationen).
Ich bin immer noch auf der Suche was einige der memory_order's für Auswirkungen haben.
Es gibt enorm wenig Info zu diesem Thema. Ein guter Blog eintrag ist zB:
http://bartoszmilewski.wordpress.com/2008/12/01/c-atomics-and-memory-ordering/
Oder auch das hier: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1525.htmEigentlich bieten sie nur die Möglichkeit zur Optimierung. Man könnte immer memory_order_acq_rel verwenden, wird aber ab und zu in unnötige fences laufen.
Ich habe in einem älteren Thread gelesen, dass du ebenfalls eine Implementierung von atomic<T> vorgenommen hast ... wie genau hast du die memory_order unterschieden?
Ich habe das alles viel viel simpler gehalten. Im Prinzip habe ich mich an boost::shared_ptr gehalten - das verwendet ja schon seit Jahren eine atomic Klasse für das Speichern des ref counts.
-
Eine sinnvolle Implementierung wird wohl immer spezielle Compiler-Intrinsics und/oder Inline-Assembler verwenden.
An manchen Stellen wird man auch OS Funktionen verwenden können. Windows hat ja z.B. einige InterlockedXxx_acq und InterlockedXxx_rel Funktionen. Und was ich vom Linux-Source so gesehen habe, da gibt's tonnenweise Fence-Funktionen.
-
Im Endeffekt bietet die Intel - Dokumentation einen sehr guten Überblick und zeigt auch gleich, dass viele der memory_order's so gar nicht umsetzbar sind, da zumindest Intel 64 / IA32 ein im vergleich zu Itanium viel "stärkeres" Ordering hat.
Im Endeffekt scheint das Wichtigste zu sein, dass die Operationen atomar ablaufen. Alles weitere kann (fast) nur ein Hint sein, wobei natürlich immer ein Miniumum zu erfüllen ist.
-
@FrEEzE2046:
Also das ist ja wohl sagenhafter Quatsch.
Natürlich sind die alle umsetzbar, und natürlich ist wichtig dass die definierte Semantik exakt eingehalten wird.Was du vielleicht falsch verstanden hast: jeder "Fence" darf stärker sein als gefordert, aber niemals schwächer. D.h. gerade mit der x86 Architektur ist es leicht die vorgeschriebenen Fences zu implementieren.
-
hustbaer schrieb:
Also das ist ja wohl sagenhafter Quatsch.
Eventuell formulierst du das beim nächsten Mal etwas wohl gewollter. Insbesondere da du nichts anderes sagst, als ich versucht habe zu sagen.
hustbaer schrieb:
jeder "Fence" darf stärker sein als gefordert, aber niemals schwächer.
Ich habe mich wohl ungeschickt ausgedrückt. Exakt das gleiche meinte ich damit, dass ein "Minimum" zu erfüllen ist. Was ich sagen wollte war nur, dass gerade bei x86 fast immer ein stärkeres Ordering ist, als beispielsweiße per memory_order enum angeführt.
Ich habe im Standard allerdings nirgendwo den Hinweis darauf gefunden, dass eine Barriere auch stärker sein darf.
-
FrEEzE2046 schrieb:
Ich habe im Standard allerdings nirgendwo den Hinweis darauf gefunden, dass eine Barriere auch stärker sein darf.
In den drafts steht immer nur dass A oder B erfüllt sein muss. Zb alle loads müssen sequentiell sein. Wenn dann aber noch mehr passiert, zb statt alle reads auf x müssen nach dem load passieren und dann sind alle reads immer nach allen loads, so ist das zwar performance technisch nicht perfekt, aber das requirement ist erfüllt.
Prinzipiell ist mit mutexen alles in std::atomic korrekt implementierbar, nur halt nicht ideal - aber den standard erfüllen würde es.
-
Shade Of Mine schrieb:
Prinzipiell ist mit mutexen alles in std::atomic korrekt implementierbar, nur halt nicht ideal - aber den standard erfüllen würde es.
Naja, unter Intel64 / IA-32 Architektur reicht schon die Verwendung des LOCK-Prefix um alle Anforderungen zu erfüllen.
Ich habe mir in der Zwischenzeit mal die std::atomic Implementation von GCC (v4.5.2) angesehen ... der angesprochene memory_order Parameter wird dort mit einem assert() geprüft (manche Funktionen erlauben nur spezielle memory_order Werte). Das finde ich höchst unglücklich, erst zur Laufzeit auf eine von vornherein ungültige Operation hingewiesen zu werden, aber das Thema hatten wir ja bereits.