std::atomic
-
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.