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 überhauptInterlockedExchangeAdd
? Standard für Getter wäre eherInterlockedCompareExchange(&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 findeExchangeAdd
in dem Zusammenhang auf jeden Fall verwirrend.
-
1. Schlechter Stil -> warum pointer statt referenz.
2. Durch volatile ist die Performance auch direkt wieder hin.
-
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?
-
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 Typlong const&
und initialisiert sie mit dem Rückgabewert vonInterlockedExchangeAdd
* -
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*
, infloat 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 Typlong
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!