Interlocked - Routinen



  • Ich möchte mich auch mal wieder zu Wort melden 😉

    Ich habe den Bedarf, die Funktionalität von eingen der aus der WinAPI bekannten Interlocked Routinen nachzubilden. Jeweils in 4 Varianten:

    1. Atomare Operation
    2. Atomare Operation mit Acquire Semantik
    3. Atomare Operation mit Release Semantik
    4. Atomare Operation mit Acquire & Release Semantik

    Als Beispiel sei mal der InterockedIncrement angeführt:

    interlocked_inc_32(target)
    {
        mov  ecx, 1
        lea  eax, target
        lock xadd, [eax], ecx
    }
    
    interlocked_inc_32_acq(target)
    {
        lfence
        mov  ecx, 1
        lea  eax, target
        lock xadd, [eax], ecx
    }
    
    interlocked_inc_32_rel(target)
    {
        sfence
        mov  ecx, 1
        lea  eax, target
        lock xadd, [eax], ecx
    }
    

    Die erte Frage wäre, ob dass überhaupt so zu lösen ist oder ob ich hier etwas missachte?
    Desweiteren stehe ich vor dem Problem der performanten Einbindung in C++ Code. Im Endeffekt bleibt mir nur der Inline Assembler (MSVC 10), da ich einen 'call' aus Performance gründen natürlich vermeiden will.
    Ich würde auch gerne auf den Funktions Epi- und Prolog verzichten und ein Makro in bestehenden C++ Code hätte ich nur ungern.



  • FrEEzE2046 schrieb:

    Desweiteren stehe ich vor dem Problem der performanten Einbindung in C++ Code. Im Endeffekt bleibt mir nur der Inline Assembler (MSVC 10), da ich einen 'call' aus Performance gründen natürlich vermeiden will.
    Ich würde auch gerne auf den Funktions Epi- und Prolog verzichten und ein Makro in bestehenden C++ Code hätte ich nur ungern.

    Am Besten du verwendest die entsprechenden intrinsics, das ist für die Performance immer das idealste und funktioniert auf allen Plattformen.



  • dot schrieb:

    Am Besten du verwendest die entsprechenden intrinsics, das ist für die Performance immer das idealste und funktioniert auf allen Plattformen.

    Ja, das weiß ich 😉 Aber in meiner Umgebung kann ich weder die Intrinsics noch die WinAPI verwenden. Deswegen möchte ich es ja nachbilden.



  • Was für eine Umgebung ist das denn?



  • dot schrieb:

    Was für eine Umgebung ist das denn?

    Es handelt sich um ein proprietäres Betriebssystem. Als System kann ein 686P+ angenommen werden.



  • nur mal als Hinweis: lock <> performance - brauchst dir keine gedanken über mögliche verluste durch den 'call' machen. Genau so kannst du dir das s/lfence sparen.



  • masm schrieb:

    Genau so kannst du dir das s/lfence sparen.

    Könntest du das erläutern? In einer Multi-Processor Umgebung, ist Acquire bzw. Release Semantik von nöten.



  • Welche Schreib/Lesezugriffe sollen in deinem Schnipseln unbedingt vor s/lfence ausgeführt werden?
    Die Synchronisation erfolgt doch durch das LOCK-Präfix, d.h. hier wird sichergestellt, dass die catches und der Speicher alle auf dem gleichen stand sind.(?)



  • masm schrieb:

    Die Synchronisation erfolgt doch durch das LOCK-Präfix, d.h. hier wird sichergestellt, dass die catches und der Speicher alle auf dem gleichen stand sind.(?)

    Nein!

    Intel schrieb:

    In a multiprocessor environment, the LOCK# signal ensures that the processor has exclusive use of any shared memory while the signal is asserted

    Es ist folgendermaßen: Schreib-Zugriffe auf ausgerichtete Adressen sind immer atomar. Wenn du dich darauf jedoch nicht verlassen willst und keine Instruktion verwendest die automatisch das LOCK-Signal veranlasst (wie z.B. XCHG), dann musst du das LOCK-Präfix benutzen um sicherzustellen, dass der ausführende Prozessor exklusiven Zugriff hat und die Operation somit atomar vollzogen werden kann.
    Welche Sicht ein anderer Prozessor hat und vor allem was mit vor- bzw. nachgelagerten Schreibzugriffen in einem Multi-Prozessor-System ist, steht auf einem ganz anderen Blatt (Stichwort weak-memory-ordering).

    Siehe dazu auch:
    http://msdn.microsoft.com/en-us/library/ff540496.aspx
    und
    http://stackoverflow.com/questions/4442934/acquire-release-pair-out-of-order-execution

    Meine Snippets sollen Implementationen für ein InterockedIncrement sein. Was Acquire oder Release Semantik benötigt ist vor bzw.- nachgelagert.



  • Für die Speicherstelle, auf die durch xadd zugreifst, ist sichergestellt, das während der Operation, sich der Wert nicht von 'außen', d.h. durch eine Anderen Prozessor ändert. Zugriffe mittels Lock werden, laut Intel, in absoluter Reihenfolge ausgeführt - d.h. meines Verständnis nach, das l/sfence keine Einfluss hat, da der Zugriff durch xadd sowieso später ausgeführt wird.



  • masm schrieb:

    Für die Speicherstelle, auf die durch xadd zugreifst, ist sichergestellt, das während der Operation, sich der Wert nicht von 'außen', d.h. durch eine Anderen Prozessor ändert

    Dem ist so. Aber wer garantiert dir, dass ein anderer Prozessor nicht eine andere Sicht auf die Speicherstelle hat (z.B. durch den L1 Cache)?
    Wie dem auch sei. Es gibt die FENCE Instruktionen ja nicht grundlos. Ebenso wenig wie die Interlocked-Routinen mit Acquire bzw. Release Suffix.
    Was wäre deine Meinung wo der Unterschied zwischen diesen liegt?



  • Intel schrieb:

    A locked instruction is guaranteed to lock only the area of memory defined by the destination operand, but may be interpreted by the system as a lock for a larger memory area.

    Okay, dementsprechend sind die Operationen für die Zieladresse definitiv geschützt und nach der Ausführung global sichtbar.

    Dennoch bräuchte ich die FENCE Instruktionen gerade in dem Fall, dass eine "Critical Section" betreten bzw. verlassen werden soll. Es ist entscheidend, dass ein "Lock" - Objekt erst dann signalisiert einen Bereich sicher betreten bzw. verlassen zu können, wenn alle schreib bzw. lese Operationen auf die Critical Section abgeschlossen sind.



  • Implementiert man locks nicht ab besten über cmpxchg?



  • dot schrieb:

    Implementiert man locks nicht ab besten über cmpxchg?

    Wie gesagt, ggf. ist nicht sichergestellt ob alle Read/Write Operationen auf eine geteilte Resource (ich meine NICHT 'den lock') global bekannt sind.



  • FrEEzE2046 schrieb:

    Wie gesagt, ggf. ist nicht sichergestellt ob alle Read/Write Operationen auf eine geteilte Resource (ich meine NICHT 'den lock') global bekannt sind.

    Wenn das ein Problem ist dann muss man eine solche Ressource eben durch ein lock schützen, genau dafür hat man ja locks 😕



  • komisch nur, das auf meinem System (Win7-x64, Core2Duo) InterlockedIncrementAcquire als InterlockedIncrement implementiert ist. Hierzu ein interessanter Satz:

    msdn schrieb:

    On processors that do not support acquire-semantics operations, InterlockedIncrementAcquire is identical to InterlockedIncrement. On processors such as Intel Itanium-based processors, which do support these operations, InterlockedIncrementAcquire runs faster.



  • masm schrieb:

    msdn schrieb:

    On processors that do not support acquire-semantics operations, InterlockedIncrementAcquire is identical to InterlockedIncrement. On processors such as Intel Itanium-based processors, which do support these operations, InterlockedIncrementAcquire runs faster.

    Ich kenne diesen Satz. Jedoch heißt "not supported" nicht "not needed". Ich glaube wir verstehen uns falsch: Dieser Artikel beschreibt den Nuten der Acquire bzw. Release Semantik hinsichtlich dem Memory-Ordering ziemlich gut:
    http://blogs.msdn.com/b/kangsu/archive/2007/07/16/volatile-acquire-release-memory-fences-and-vc2005.aspx



  • ok - vieleicht missversteh ich das wirklich ... aber wie ich schon sagte, erzwingen LOCKs eine 'total order' (nur für WB Speicher)


Anmelden zum Antworten