Memory Barriers/Fences



  • Skym0sh0 schrieb:

    Oha, du öffnest gerade die Büchse der Pandora.

    Genau. Entering a World of Pain.

    Skym0sh0 schrieb:

    Eine allumfassende Antwort zu geben ist also schwierig und dafür habe ich gerade nicht die Zeit.

    Ich würde sogar sagen quasi unmöglich.



  • Haltet ihr beiden es für nötig, die Büchse der Pandora zu öffnen aka die "World Of Pain" zu entern, wenn man vernünftige Multithreading-Anwendungen schreiben will?

    Das ganze Zeug haben sich ja Menschen ausgedacht, das heißt, dass es 1. nicht unbegreifbar ist und 2. nicht unendlich viel Wissen sein kann, dass man sich aneignen kann. Ist es wirklich so dramatisch umfangreich?



  • Speicherer schrieb:

    Ich hab mich geirrt und du liegst richtig: CPUs tun nicht so, als würden bestimmte Speicherzugriffe gemacht werden.

    Ne, ich würde sagen das war schon richtig. Also kommt halt drauf an wie du es gemeint hast.
    Auf bestimmten CPUs kann es quasi beliebig lange dauern bis ein Store wirklich im Hauptspeicher landet - und auch beliebig lange dauern bis der neu geschriebene Wert von einem anderen Core "gesehen" werden kann. Dabei wird jeder Store zwar üblicherweise schon in den 1st Level Cache geschrieben, aber von dort nicht unbedingt in weiter Cache-Levels oder gar in den Hauptspeicher. Und da jeder Core üblicherweise seinen eigenen, unabhängigen 1st Level Cache hat...

    Auf x86 wirst du sowas nicht sehen, da x86 vollständig automatisch kohärente Caches hat. Auf ARM CPUs dagegen passiert das andauernd.

    Speicherer schrieb:

    Sie können bloß die Reihenfolge der durch das Programm angefragten Speicherzugriffe ändern, solange das Verhalten hinterher immer noch das gleiche ist wie das Programm es erwartet.

    Da können sie auch.

    Speicherer schrieb:

    Memory Barriers sorgen dafür, dass die Instruktionen vor der Barriere auf jeden Fall vor den Instruktionen nach der Barriere ausgeführt werden.

    Wieder: Dafür sorgen sie auch. Die Sache ist aber eben leider nicht so einfach. Google dazu vielleicht mal "acquire and release semantics". Bzw. lies dich zum Thema Cache-Kohärenz schlau. Viele ARM CPUs haben z.B. keine "automatisch" kohärenten Caches ("hardware coherency").



  • Speicherer schrieb:

    Haltet ihr beiden es für nötig, die Büchse der Pandora zu öffnen aka die "World Of Pain" zu entern, wenn man vernünftige Multithreading-Anwendungen schreiben will?

    Das ganze Zeug haben sich ja Menschen ausgedacht, das heißt, dass es 1. nicht unbegreifbar ist und 2. nicht unendlich viel Wissen sein kann, dass man sich aneignen kann. Ist es wirklich so dramatisch umfangreich?

    Sagen wir mal so, am Wochenende gab es auf der Meeting C++ diesen Vortrag*. Das sind die Basics, die mich als ich sie (vor Jahren) zum ersten mal gehört hab, total perplex gemacht haben. Danach kam dieser Vortrag und da dachte ich "ach scheiss auf performance, man kanns eh nicht richtig und gut machen".

    Umgekehrt, wenn du dafür sorgst, dass die Google Algorithmen auch nur 1% schneller laufen, hast du damit über die sinkenden Energiekosten das Jahresgehalt für eine ganze Entwicklungsabteilung rausgeholt.

    Ich sitze im Moment an einem Problem, wo die sequentielle Berechnung ca. 1 Gigaflop/s macht. Meine erste parallele Implementierung mit 48 Kernen hats auf ca 0,0003 GFlops/s geschafft. Meine weiteren Versuche werden langsam besser und sind grad bei 3-4 GFlops/s. Ich schätze, dass die Fahnenstange bei etwa 10 oder sogar 20 GFlops/s zu Ende ist. Aber da sind wir immer noch weit entfernt von einem halbwegs linearen Speedup.

    Also ja, es ist kompliziert und schwierig. Aber wenn du Bock drauf hast, wieso nicht. Ist halt kein Thema um mal eben ne Woche zu investieren und dann fertig zu sein.



  • Speicherer schrieb:

    Haltet ihr beiden es für nötig, die Büchse der Pandora zu öffnen aka die "World Of Pain" zu entern, wenn man vernünftige Multithreading-Anwendungen schreiben will?

    Nein. Man kommt schön mit Mutexen + Condition-Variablen aus, damit kann man alles machen.
    Vielleicht noch Atomics für so Sachen wie Counter (Reference-Counter, ...), denn wenn man wirklich NUR einen Counter atomar anpassen will, dann wäre Mutex Lock+Unlock vermutlich wirklich ein bisschen viel Overhead. Obwohl ich es an vielen Stellen trotzdem so mache, einfach weil dieser Overhead trotzdem oft völlig vernachlässigbar ist, weil das Drumherum noch viel viel länger dauert.

    Speicherer schrieb:

    Das ganze Zeug haben sich ja Menschen ausgedacht, das heißt, dass es 1. nicht unbegreifbar ist und 2. nicht unendlich viel Wissen sein kann, dass man sich aneignen kann. Ist es wirklich so dramatisch umfangreich?

    Jain. Aber du kannst dir ja mal den C++ Standard durchlesen. Da ist u.A. das C++ Memory-Model beschrieben. Also alle Regeln die es zu dem Thema zu beachten gibt. Doof dabei ist bloss dass der in "Standardese" geschrieben ist, und daher einigermassen schwer verständlich. Und da es ein Standard ist, und kein Lehrbuch, ist der Text auch gespickt mit lauter Querverweisen, was das Lesen nochmal umständlicher macht.



  • Ich danke euch beiden, jetz hab ich erst mal genug Stoff zum Durchackern.

    hustbaer schrieb:

    Speicherer schrieb:

    Haltet ihr beiden es für nötig, die Büchse der Pandora zu öffnen aka die "World Of Pain" zu entern, wenn man vernünftige Multithreading-Anwendungen schreiben will?

    Nein. Man kommt schön mit Mutexen + Condition-Variablen aus, damit kann man alles machen.
    Vielleicht noch Atomics für so Sachen wie Counter (Reference-Counter, ...), denn wenn man wirklich NUR einen Counter atomar anpassen will, dann wäre Mutex Lock+Unlock vermutlich wirklich ein bisschen viel Overhead.

    Es ist also durch die beiden Konzepte Mutex und Atomic immer sichergestellt, dass irgendwelche Cachings, Memory Barriers usw. so abgehandelt werden, dass man immer den aktuellen Wert hat?



  • Speicherer schrieb:

    Ich danke euch beiden, jetz hab ich erst mal genug Stoff zum Durchackern.

    hustbaer schrieb:

    Speicherer schrieb:

    Haltet ihr beiden es für nötig, die Büchse der Pandora zu öffnen aka die "World Of Pain" zu entern, wenn man vernünftige Multithreading-Anwendungen schreiben will?

    Nein. Man kommt schön mit Mutexen + Condition-Variablen aus, damit kann man alles machen.
    Vielleicht noch Atomics für so Sachen wie Counter (Reference-Counter, ...), denn wenn man wirklich NUR einen Counter atomar anpassen will, dann wäre Mutex Lock+Unlock vermutlich wirklich ein bisschen viel Overhead.

    Es ist also durch die beiden Konzepte Mutex und Atomic immer sichergestellt, dass irgendwelche Cachings, Memory Barriers usw. so abgehandelt werden, dass man immer den aktuellen Wert hat?

    Bei Mutexen immer. Bei atomics nur, wenn du die (Default) sequentiell konsistenten Operationen wählst.



  • Speicherer schrieb:

    [*]Müssen Konzepte für die Synchronisierung, wie Mutexe, zwingend Memory Barriers nutzen? Wenn ja, wie genau und wieso müssen sie das tun?

    kommt drauf an aus welcher sicht. es gibt leute die darauf verzichten, denn fuer das primitiv an sich ist es vielleicht sogar wirklich nicht noetig damit es funktioniert, aber dem "verbraucher" wuerde es in die falle treiben, denn eine sychronization von threads klappt auf programflussebene, daten waeren damit aber nicht zwingend synchronisiert.

    Speicherer schrieb:

    [*]Was passiert, wenn die CPU auf eine Memory Barrier stößt, dadurch dann alle Speicheranweisungen tatsächlich ausführt und während dieser Ausführung weitere Speicherzugriffe auf einer anderen CPU stattfinden? Oder kann, so Anweisungen aufgrund einer Barrier ausgeführt werden, auf keiner weiteren CPU eine Speicheranweisung stattfinden?

    die Barrier hat nur mit der CPU zu tun die diese ausfuehrt, diese flusht alle speicheroperationen und wartet bis diese bestaetigt sind. Die stores gehen dabei in der cache, das reicht, weil ab dort cache kohärenz mechanismen greifen. solange die daten in der cpu schweben, ist das nicht der fall.

    Speicherer schrieb:

    [*]Muss ich als Entwickler von Multithreading-Anwendungen immer Memory Barriers verwenden (ob nun in Form eines Mutex [falls Mutexe das denn tun] oder wirklich explizit mit einer Barrier-Anweisung), wenn ich die selbe Variable in zwei Threads verwende?

    eine CPU bzw ein core alleine fuer sich ist kohärent. kommunizierst du mit anderen threads, wirst du eh explizit werden muessen. das ist grundsaetzlich langsam und du solltest diese kommunikation minimieren, denn egal welche tolle hexerei du zum synchronisieren benutzt, wenn du es uebertreibst, bist du langsammer als auf einem core ohne threads.
    wenn du also schon bei dem grundprinzip bist diese kommunikation zu minimieren, benutzte fuer die 0.01% in deinem code vom system gegebene synchronisationsmechanismen, keine "super schnellen 3rd party source snippets". vielleicht machst du damit die 0.01% von deinem program sogar 10mal langsammer, aber du wirst es immer noch nicht messen koennen. (dauert vermutlich bis du in einen fall kommst wo es unumgaenglich ist sich damit zu befassen).

    Speicherer schrieb:

    [*]Gibt es außer Locking (Mutexe, Semaphores usw.) und Memory Barriers weitere Konzepte, die ich als Entwickler von Multithreading-Anwendungen verstehen sollte, damit ich auch wirklich genau weiß, was ich tue?[/list]

    das wichtigste konzept ist dass du dich auf bestehende dinge verlaesst. es ist all zu einfach der versuchung zu verfallen etwas selbst zu implementieren. denn in der theorie klingt das einfach und es wird fataller wenn du ein besserer programmierer bist, dann traust du dir das ploetzlich zu womit du keine erfahrung hast.
    es wird schiefgehen beim ersten mal! mach das nur wenn du erfahren bist, damit leben kannst dass es schief geht und zeit hast fuer den lernprozess.

    hustbaer schrieb:

    Auf x86 wirst du sowas nicht sehen, da x86 vollständig automatisch kohärente Caches hat.

    du kannst das auf x86 sehen und auf x64 sogar noch oefter. Es geht nicht nur um caches, sondern eher um die load- und store-queues. Deswegen gibt es die "mfence" instruktion.

    Skym0sh0 schrieb:

    Ich sitze im Moment an einem Problem, wo die sequentielle Berechnung ca. 1 Gigaflop/s macht. Meine erste parallele Implementierung mit 48 Kernen hats auf ca 0,0003 GFlops/s geschafft. Meine weiteren Versuche werden langsam besser und sind grad bei 3-4 GFlops/s. Ich schätze, dass die Fahnenstange bei etwa 10 oder sogar 20 GFlops/s zu Ende ist. Aber da sind wir immer noch weit entfernt von einem halbwegs linearen Speedup.

    das klingt interesant, kannst/darfst du dazu mehr sagen? 🙂
    wenn solche probleme gut zu kapseln sind, faende ich sowas als foren-challenge echt cool.



  • rapso schrieb:

    hustbaer schrieb:

    Auf x86 wirst du sowas nicht sehen, da x86 vollständig automatisch kohärente Caches hat.

    du kannst das auf x86 sehen und auf x64 sogar noch oefter. Es geht nicht nur um caches, sondern eher um die load- und store-queues. Deswegen gibt es die "mfence" instruktion.

    Ein normaler (=nicht MMX/...) Load auf x86 hat Acquire-Semantik.
    Ein normaler (=nicht MMX/...) Store auf x86 hat Release-Semantik.
    Für amd64 gilt das selbe.
    Ich meine mich zu erinnern dass es Ausnahmen bei bestimmten MMX/SIMD Registern und den String-Befehlen gab, aber dazu kann ich auf die Schnelle nix finden. Kann auch sein dass das nur ältere CPU Modelle betraf und irgendwann gefixed wurde.

    Was mfence angeht: das braucht man auf x86/amd64 AFAIK nur wenn man irgendwo wirklich einen "full fence" braucht. Also einen Fence wo auch Stores nicht hinter Loads verschoben werden dürfen - was das einzige Reordering ist was x86/amd64 macht. Oder wenn man warten will/muss bis irgendwelche Daten für z.B. DMA Operationen sichtbar sind.

    Zumindest Clang, GCC und ICC sehen das auch so wie ich:
    Clang https://godbolt.org/g/PuR6Bl
    GCC https://godbolt.org/g/lVfmZ8
    ICC https://godbolt.org/g/AqAEYh

    Ich kann daher nicht wirklich nachvollziehen was du schreibst.



  • hustbaer schrieb:

    Auf x86 wirst du sowas nicht sehen, da x86 vollständig automatisch kohärente Caches hat. ...
    Was mfence angeht: das braucht man auf x86/amd64.. wo auch Stores nicht hinter Loads verschoben werden dürfen

    👍
    deswegen kann es auch auf x86, wie du geschrieben hast, "beliebig lange dauern bis der neu geschriebene Wert von einem anderen Core "gesehen" werden kann". (Wobei du recht hast, die Caches sind kohärent, das Problem liegt davor).



  • Da hab ich mich wieder mal ungenau ausgedrückt. Hmpf. Was ich meinte war: Auf x86 wird ein Core keine "falsche" Reihenfolge von Stores von anderen Cores sehen. Die Korrektheit des Programms wird davon also nicht beeinflusst. (Was auf anderen CPUs sehrwohl der Fall sein kann.)

    Was theoretisch natürlich passieren kann, ist dass das Programm verzögert wird, weil Core B einen Store von Core A erst viel zu spät mitbekommt, und daher länger auf irgendwas wartet als notwendig wäre.

    Ich nehme aber an dass auch aktuelle x86/amd64 CPUs hier eine recht knappes Limit setzen wie lange sie einen Store maximal verzögern. Liege ich damit falsch? Falls du meinst ja, und dazu Papers/Artikel/Doku-Links hast, würde mich das sehr interessieren!

    ----

    Aber nochmal zurück zu den Caches. Entsteht das Problem bei ARM auch nur durch Stages vor den Caches? Ich dachte immer da sind wirklich (auch) die Caches das Problem, aber wenn ich es google finde ich da widersprüchliche Informationen.



  • hustbaer schrieb:

    Da hab ich mich wieder mal ungenau ausgedrückt. Hmpf. Was ich meinte war: Auf x86 wird ein Core keine "falsche" Reihenfolge von Stores von anderen Cores sehen. Die Korrektheit des Programms wird davon also nicht beeinflusst. (Was auf anderen CPUs sehrwohl der Fall sein kann.)

    Stores von anderen Cores können hinausgezoegert sein. wenn also zwei cores an verschiedene addressen einen wert schreiben und dann den wert vom anderen core auslesen, kann es sein, dass beide den store des jeweils anderen nicht sehen und damit eine "falsche" reihenfolge entsteht. eine direkte falsche store reihenfolge von stores eines einzigen cores sind nicht moeglicht, das stimmt.

    Aber nochmal zurück zu den Caches. Entsteht das Problem bei ARM auch nur durch Stages vor den Caches? Ich dachte immer da sind wirklich (auch) die Caches das Problem, aber wenn ich es google finde ich da widersprüchliche Informationen.

    Das kommt auf die einzelnen prozessoren an und sind zwei unterschiedliche Probleme:
    caches
    Manche haben nicht synchronisierte caches, dann reicht eine barrier nicht aus und du musst einen kompletten flush machen, was natuerlich sau teuer sein kann (APUs mit/trotz x86 als beispiel).
    Manche haben 'partial flushes', dann wird der ganze cache zwar durchgegangen, aber alles ausserhalb einer address range wird ignoriert und kostet nur 1cycle.
    Auf MIPS gibt es oft ein 'non cacheable' bit in der addresse, sodass du am cache vorbei schreiben kannst um den flush zu umgehen, sowas wie

    out[index+0x80000000]=..
    

    .
    auf manchen architekturen kannst du in der MMU festlegen, ob pages kohärent sind.
    auf D3D12/Vulkan musst du barriers verbauen, was aber cache flushes sind (Barrier wohl nur aus semantik gruenden genannt).
    Unter OGL kannst du in ein und dieselbe texture rendern aus der du samplest (natuerlich aus einem anderen "rect"), aber weil die API die sub-rects nicht verwalten kann, musst du die caches flushesn (also innerhalb desselben prozessors), auch barrier genannt:
    https://www.opengl.org/registry/specs/NV/texture_barrier.txt

    load/store queues
    das sind aber die klassischen memory "barrieres". die meisten architekturen, trotz kohärenten caches (ARM/MIPS/PowerPC) reordern stores und loads nach belieben (solange es fuer den eigentlichen core die richtige reihenfolge ist).
    caches sind kohärent, weil du garnicht wissen kannst was drinnen steckt, denn die sind transparent verwaltet, gerade in einem multitasking OS. Ein interrupt bzw. OS-Scheduler muessen auch memory barriers erzwingen. abseits davon kann dein programm eigentlich ohne barriers problemfrei laufen, einzig in den kurzen momenten wo du threads synchronisierst sind barriers noetig. Das senkt extrem den druck auf die memory-queues.

    Oft haben die Prozessoren mit relaxed memory ordering auch keine atomics, sondern nur reservations. Auch das ist extrem druck senkend, denn nur 'collisions' sind teuer, waehrend bei Atomics immer die ganze speicherarchitektur synchronisiert sein muss.

    auf der anderen seite sind Loads auf vorherigen Stores, z.b. weil du float->int castest, extrem teuer. Auf x86 durch store-forwarding relativ billig, denn wegen den in-order queues und des stacks als parameter buffer ist die situation zu gaengig als dass sie nicht optimiert werden wuerde (auf MIPS/ARM/PowerPC gibt man parameter eher ueber register weiter).



  • rapso schrieb:

    hustbaer schrieb:

    Da hab ich mich wieder mal ungenau ausgedrückt. Hmpf. Was ich meinte war: Auf x86 wird ein Core keine "falsche" Reihenfolge von Stores von anderen Cores sehen. Die Korrektheit des Programms wird davon also nicht beeinflusst. (Was auf anderen CPUs sehrwohl der Fall sein kann.)

    Stores von anderen Cores können hinausgezoegert sein. wenn also zwei cores an verschiedene addressen einen wert schreiben und dann den wert vom anderen core auslesen, kann es sein, dass beide den store des jeweils anderen nicht sehen und damit eine "falsche" reihenfolge entsteht.

    Hmmm, OK. Danke für das Beispiel! Dann muss ich wohl zugeben dass auch die genauere Formulierung von dem was ich schreiben wollte nicht wirklich stimmt. Mist 😃

    Das wäre jetzt also ein Beispiel wo acquire/release vs. sequentially consistent wirklich nen Unterschied macht. Wenn alle Operationen mit seq_cst gemacht würden, dürfte/könnte max. einer der beiden Cores beim Load sehen dass der andere noch nicht gestored hat.
    Die mMn. korrekte Interpretation bei acquire/release, wo der von dir genannte Effekt auftreten kann, wäre: jeder Core sieht dass der andere noch nicht fertig ist, was quasi (nur) so zu verstehen ist dass das Ergebnis des anderen noch nicht "angekommen" ist. Daraus darf bzw. kann man aber keine absolute Reihenfolge (re)konstruieren (sequential consistency).

    rapso schrieb:

    Manche haben nicht synchronisierte caches, dann reicht eine barrier nicht aus und du musst einen kompletten flush machen, was natuerlich sau teuer sein kann (APUs mit/trotz x86 als beispiel).

    Hm. Also ich würde schon davon ausgehen dass ne C++ Barrier auf solchen Architekturen dann falls nötig den Cache flusht. Weil der arme (Standard-)C++ Programmierer ja nix davon weiss auf was für einer Architektur sein Code läuft, und ausser den Barriers bzw. Atomics mit eingebautem Memory-Ordering nix zur Verfügung hat. Dass das auf Maschinencode-Ebene mehr als nur ne Barrier-Instruktion sein kann ist mir klar.

    rapso schrieb:

    abseits davon kann dein programm eigentlich ohne barriers problemfrei laufen, einzig in den kurzen momenten wo du threads synchronisierst sind barriers noetig. Das senkt extrem den druck auf die memory-queues.

    Schon klar, es geht ja hier ausschliesslich um den Fall wo man Threads synchronisiert. Nur da schreibt man normalerweise auch keine expliziten Barriers hin, sondern verlässt sich auf die acquire/release Semantik von Mutexen bzw. Atomics. Also zumindest bei dem was ich so programmiere.

    rapso schrieb:

    Oft haben die Prozessoren mit relaxed memory ordering auch keine atomics, sondern nur reservations.

    Sind "reservations" die Operationen wo man quasi sagt "schreib das mal zurück, aber nur wenn es sich seit dem Load von geradeeben nicht geändert hat, und sag mir dann ob's geklappt hat"?
    Also so ne Art Mikro-Transaktionen.



  • hustbaer schrieb:

    Die mMn. korrekte Interpretation bei acquire/release, wo der von dir genannte Effekt auftreten kann, wäre: jeder Core sieht dass der andere noch nicht fertig ist, was quasi (nur) so zu verstehen ist dass das Ergebnis des anderen noch nicht "angekommen" ist. Daraus darf bzw. kann man aber keine absolute Reihenfolge (re)konstruieren (sequential consistency).

    ja, sowas simples koennte dann ein deathlock sein

    void ThreadSync(int currentID)
    {
       done[currentID] = true;
       //mfence
       while(!done[currentID^1]);
    }
    

    natuerlich wird frueher oder spaeter der store durchkommen, spaetestens wenn der Scheduler kurz aufwacht. aber hypothetisch kann das ohne mfence fuer immer laufen, obwohl vom programfluss her nur einer von zwei threads spinnen duerfte.

    rapso schrieb:

    Manche haben nicht synchronisierte caches, dann reicht eine barrier nicht aus und du musst einen kompletten flush machen, was natuerlich sau teuer sein kann (APUs mit/trotz x86 als beispiel).

    Hm. Also ich würde schon davon ausgehen dass ne C++ Barrier auf solchen Architekturen dann falls nötig den Cache flusht. Weil der arme (Standard-)C++ Programmierer ja nix davon weiss auf was für einer Architektur sein Code läuft, und ausser den Barriers bzw. Atomics mit eingebautem Memory-Ordering nix zur Verfügung hat. Dass das auf Maschinencode-Ebene mehr als nur ne Barrier-Instruktion sein kann ist mir klar.

    Ich weiss nicht, ob der standard bei einer Barrier einen flush macht. Ohne flush waere das recht unsicher, aber ein impliziter flush wuerde auch verstecken wie unglaublich teuer die sache ist.
    Ueberall wo ich damit 'gespielt' habe, war das flush explizit. (waren aber system libs, nicht c++ std)

    rapso schrieb:

    Oft haben die Prozessoren mit relaxed memory ordering auch keine atomics, sondern nur reservations.

    Sind "reservations" die Operationen wo man quasi sagt "schreib das mal zurück, aber nur wenn es sich seit dem Load von geradeeben nicht geändert hat, und sag mir dann ob's geklappt hat"?
    Also so ne Art Mikro-Transaktionen.

    gibt zwei varianten, entweder es gibt einen explizites load und store mit reservation, oder wirklich eine reservation.
    solange es keine collision gibt, laeuft das super schnell, weil die operationen spekulativ gemacht werden und die hardware implementierung viele moeglichkeiten frei laesst. gerade die expliziten reservations erlauben komplexere algorithmen, weil du auf z.b. 128byte einer cacheline arbeiten kannst (powerpc). z.b. koenntest du in einem Task system mehrere queues mit verschiedenen prioritaeten haben, bei begin/end offset auf eine queue von je 4byte hast du 16 Priority levels. oder load balancing beim "insert" von jobs in 16 consumer.

    nachteil ist, dass die prozessoren oft nur eine reservation vertragen, hast du also konflikte, kann es passieren dass prozessoren im infinity loop gefangen sind, weil es keine chance gibt dass irgendein thread die reservation bis zum reservation-clear behaelt. du musst also ein "fail counter" haben und wenn der irgendwas uebersteigt, einen random "sleep" aus nops. 🙄

    Intels TSX ist eine "Pro" version von reservations. Da ist aber viel magic die das system unvorhersehbar machen. z.B. wird TSX vom cache-system gestuetzt. aber beim hyper threading teilen sich zwei den L1, die reservation kann also kaputt gehen weil der andere HT die assoziativitaet vom L1 ueberspannt.



  • rapso schrieb:

    ja, sowas simples koennte dann ein deathlock sein

    void ThreadSync(int currentID)
    {
       done[currentID] = true;
       //mfence
       while(!done[currentID^1]);
    }
    

    natuerlich wird frueher oder spaeter der store durchkommen, spaetestens wenn der Scheduler kurz aufwacht. aber hypothetisch kann das ohne mfence fuer immer laufen, obwohl vom programfluss her nur einer von zwei threads spinnen duerfte.

    Ja, genau der Punkt interessiert mich. Ob das bei x86/amd64 wirklich ewig laufen dürfte. Weil ich glaube dass das nicht so ist, also dass die CPU sich hier "selbst beschränkt" was die Verzögerung von Stores angeht.



  • hustbaer schrieb:

    Ja, genau der Punkt interessiert mich. Ob das bei x86/amd64 wirklich ewig laufen dürfte. Weil ich glaube dass das nicht so ist, also dass die CPU sich hier "selbst beschränkt" was die Verzögerung von Stores angeht.

    Ich glaube die CPU muss sich nicht beschraenken, die store queue hat nur 4 elemente bei Intel und es gibt nicht wirklich einen grund weshalb die abarbeitung dieser ewig hinausgezoegert werden koennte.
    Bei anderen prozessoren wo store reordert werden duerfen koenntest du vielleicht provozieren dass die neuen stores guenstiger liegen und deswegen bevorzugt werden. beim x86 kannst du weder absichtlich noch ungewollt irgendwas am store hindern, afaik.

    hmm, worst case waere vielleicht, wenn 7 andere cores in dieselbe cacheline schreiben wollen, damit wuerdest du ping-pong der cacheline ausloesen, aber es waere eher probalistisch welcher core erfolgreich schreibt. core0 wuerde nicht ewig pendeln.



  • Ob absichtlich oder nicht, es kann auf jeden Fall nicht wirklich sehr lange dauern. Die Info finde ich wichtig, denn das heisst man kann auf x86/amd64 ruhig weiterhin das Freigeben eines SpinLock per ganz normalem mov machen - was billiger ist als ein xchg und daher mMn. vorzuziehen.


Anmelden zum Antworten