InterlockedGet/SetFloat Einzeiler



  • Ist das nicht schön? 😋

    float InterlockedGetFloat(float volatile* target)
    {
    	return *reinterpret_cast<const float*>(&static_cast<const long&>(InterlockedExchangeAdd(reinterpret_cast<long volatile*>(target), 0)));
    }
    
    void InterlockedSetFloat(float volatile* target, float value)
    {
    	InterlockedExchange(reinterpret_cast<long volatile*>(target), *reinterpret_cast<long*>(&value));
    }
    


  • Nein, ist nicht schön.
    Und wieso überhaupt InterlockedExchangeAdd ? Standard für Getter wäre eher InterlockedCompareExchange(&p, 0, 0) .



  • Warum nicht schön? Kann das Probleme machen?
    Und CompareExchange hat 2 Parameter, ich denke ExchangeAdd ist performanter.



  • mhat schrieb:

    Warum nicht schön? Kann das Probleme machen?

    Nicht schön != kann Probleme machen

    mhat schrieb:

    Und CompareExchange hat 2 Parameter, ich denke ExchangeAdd ist performanter.

    Denk was du willst. Wenn du es wissen willst, dann mach nen kleinen Benchmark und probier es aus.
    Ich finde ExchangeAdd in dem Zusammenhang auf jeden Fall verwirrend.



  • 1. Schlechter Stil -> warum pointer statt referenz.
    2. Durch volatile ist die Performance auch direkt wieder hin.


  • Mod

    warum pointer statt referenz.

    Warum Referenz statt Zeiger?

    float InterlockedGetFloat(float volatile* target)
    {
        return *reinterpret_cast<const float*>(&static_cast<const long&>(InterlockedExchangeAdd(reinterpret_cast<long*>(target), 0)));
    }
    

    Ist aber sowieso undefiniertes Verhalten, wegen strict aliasing.



  • Pointer und volatile weil InterlockedExchangeAdd auch nen volatile pointer nimmt.
    Brauche ich volatile also nur im Parameter?

    UB generell oder bei MSVC nicht? 🙂
    Kannst du das mit strict aliasing genauer erklären? Und wie könnte ich es verbessern?


  • Mod

    Kannst du das mit strict aliasing genauer erklären?

    §3.10/10 schrieb:

    If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
    — the dynamic type of the object,
    — a cv-qualified version of the dynamic type of the object,
    — a type similar (as defined in 4.4) to the dynamic type of the object,
    — a type that is the signed or unsigned type corresponding to the dynamic type of the object,
    — a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,
    — an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a subaggregate or contained union),
    — a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,
    — a char or unsigned char type.

    Was hier passiert, ist folgendes:

    • static_cast<const long&>(..) erstellt eine Temporary vom Typ long const& und initialisiert sie mit dem Rückgabewert von InterlockedExchangeAdd * -
      dabei wird

    • eine Temporary erstellt (vom Typ long ), mit dem Initializer initialisiert, und

    • die temporäre Referenz wird an diese Temporary gebunden.

    • Anschließend castest du einen Zeiger auf diese Temporary, also einen long const* , in float const* und dereferenzierst ihn.

    • Und direkt darauf folgend gibt es eine lvalue-to-rvalue conversion, was einen Zugriff auf das Objekt darstellt.

    ➡ Da du damit durch ein glvalue (genauer ein lvalue vom Typ float const ) auf die an die Const-Referenz gebundene Temporary vom Typ long zugreifst, ist es nach obigem Abschnitt UB.

    ~* Das ist nicht vom Standard gefordert, aber der sagt dass das Verhalten dem gleichen muss.~

    UB generell oder bei MSVC nicht?

    Ich kann nur sagen, dass der Standard keine Anforderungen an das Verhalten dieses Codes stellt. Bei GCC gibt es -fno-strict-aliasing, vielleicht gibt es ähnliches bei VC++.

    Und wie könnte ich es verbessern?

    Ich habe von der WinAPI und von den Funktionen keine Ahnung. Allerdings verstehe ich nicht, warum das Dereferenzieren von einem float volatile* nicht atomar ist? Und man dafür eine derart komplizierte Herangehensweise braucht (WinAPI)?



  • Danke erstmal, diese Sprache ist echt ne Wissenschaft 😮
    So ganz verstehe ich es noch nicht, außerdem muss es doch ne Möglichkeit für floats geben.
    So habe ich es früher gemacht, ist aber dann doch auch UB (nach std):

    float InterlockedGetFloat(float volatile* target)
    {
    	long value = InterlockedCompareExchange(reinterpret_cast<long volatile*>(target), 0, 0);
    	return *reinterpret_cast<float*>(&value);
    }
    

    Und wo muss man nun volatile setzen und wo nicht?

    Das Problem ist denke ich reordering, nicht atomicity.



  • @mhat
    Das verbleibende Strict Aliasing Problem behebst du indem du memcpy() verwendest.

    Arcoth schrieb:

    Allerdings verstehe ich nicht, warum das Dereferenzieren von einem float volatile* nicht atomar ist? Und man dafür eine derart komplizierte Herangehensweise braucht (WinAPI)?

    Warum sollte es das sein?
    Der C++ Standard sagt darüber nix, und irgendwelche Windows Standards vermutlich auch nicht.
    Wenn dann wäre hier der x86/amd64 "Standard" gefragt.
    Ob der atomic float reads hat weiss ich nicht. Ist dann aber nicht protiertbar - also wieso überhaupt gross darüber nachdenken?



  • Aber in dem Fall würde memcpy doch die Interlocked-Funktionalität zunichtemachen. Ich muss die Adresse des floats direkt übergeben, und nicht mit Zwischenvariablen arbeiten, sonst macht der lock doch keinen Sinn mehr?!



  • Nö, so:

    float InterlockedGetFloat(float volatile* ptr)
    { 
         static_assert(sizeof(float) == sizeof(long), "float <-> long size mismtach");
    
         long lval = InterlockedCompareExchange(reinterpret_cast<long volatile*>(ptr), 0, 0); 
    
         float fval;
         memcpy(&fval, &lval, sizeof(float));
         return fval;
    }
    


  • Warum diese Asymmetrie? Sollte man memcpy() nicht auch einsetzen, um den float in einen long zu konvertieren?



  • Nein, sollte man nicht.
    InterlockedCompareExchange ist atomar und ein Fence.
    memcpy ist keines von beiden.

    Wenn du direkt memcpy zum lesen verwendest, kannst du die ganze Funktion auch gleich weglassen und die Aufrufe durch *ptr ersetzen.



  • SetFloat demnach:

    void InterlockedSetFloat(float volatile* target, float value)
    {
    	static_assert(sizeof(float) == sizeof(long), "float <-> long size mismatch");
    
    	long lval;
    
    	memcpy(&lval, &value, sizeof(long));
    
    	InterlockedExchange(reinterpret_cast<long volatile*>(target), lval);
    }
    

    Aber ich dachte, der reinterpret_cast von float* nach long* wäre UB.. oder nicht, weil da gar nicht per lvalue zugegriffen wird? Aber indirekt ja doch (in der Interlocked-Funktion dann) ?!



  • Ja das ist alles so ne Sache...
    So wirklich sauber ist es nicht, aber ich behaupte mal es wird funktionieren.
    Ob es laut Standard OK ist oder nicht weiss ich ehrlich gesagt nicht. So wie ich es verstehe schon, weil man ja nur den Zeiger rumcastet.

    Der eigentliche Zugriff passiert ja dann in Interlocked(Compare)Exchange, und da das OS Funktionen sind, darf sich mMn. die C++ Implementierung nicht daran stossen was die macht.

    Besser wäre natürlich gleich std::atomic<float> zu verwenden.
    Also falls dein Compiler das unterstützt, dann vergiss den ganzen Interlocked-Quargel, und nimm das!


Log in to reply