Lesen/Schreiben von Speicher Multithreaded
-
Na dann gilt eh beides machen und dann profilen, weil umsonst sind die atomics auch nicht.
Aber ne Lösung für parallele Lesezugriffe ... da muss ich jetzt auch erstmal nachdenken.
-
Was den Schreibzugriff angeht, bin ich glaube schon etwas weiter:
bool DataAccessControl::startWriteAccess() { // Warten auf Ende einer anderen Schreib-Operation, falls vorhanden int32_t oldValue = 0; while ( !WriteAccessCounter.compare_exchange_weak( oldValue, 1 ) ) { // Wenn wir hier landen ist, oldValue nicht mehr 0. 0 Ist aber unser Erwartungswert, also setzen wir ihn wieder oldValue = 0; } // Warten auf das Ende aller Leseoperationen, falls vorhanden while( ReadAccessCounter > 0 ); return true; }
Das "compare_exchange_weak" wartet quasi darauf, dass der WAC == 0 wird und setzt dann direkt den wert 1, ohne das dazwischen ein anderer Thread reingrätschen kann.
Hinterher warte ich ganz normal darauf, dass der RAC auch 0 wird. Ein neuer ReadAccess kann mir da nicht mehr dazu kommen, da die Bedingung für einen ReadAccess ist, dass kein WriteAccess vorliegt, und diesen habe ich mir ja bereits gesichert.Bleibt noch der Schreibzugriff.
Dort muss ich quasi warten bis der WAC == 0 wird UND dann direkt den RAC erhöhen....
-
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
daher ist mir die Performance wichtig
Und Spinlocks sind da besser als Mutexes?
-
Der Trick ist ein einziges Atomic zu verwenden indem man ein Bit für "gibt es einen Writer" und die restlichen Bits für den Leser-Zähler verwendet.
Oder du verwendest einfach SRWLocks: https://docs.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks
Oder du verwendest einfachstd::shared_mutex
: https://en.cppreference.com/w/cpp/thread/shared_mutexWie würde man sowas (performant) erledigen?
Auf Windows: SRWLocks
Ansonsten: erstmal gucken wie schnellstd::shared_mutex
ist, sollte schnell genug sein
Ansonsten: selbst schreiben, wie oben angedeutet
-
Ich fand den Artikel seinerzeit interessant:
https://webkit.org/blog/6161/locking-in-webkit/
Weiß jetzt aber nicht, ob sie das immer noch verwenden.
Die SRW Locks können auch relativ teuer sein. Und die shared_mutex Implementierung von VC++ verwendet die auch.
Das sind aber schon extreme Optimierungen. Wenn sowas wirklich eine größere Rolle spielt, stimmt eher was mit dem restlichen Code nicht.
-
Da hier mehrmals das Word Spinlock gefallen ist hier nur ein Hinweis wieso so was im userspace nicht gut ist:
https://mjtsai.com/blog/2020/01/06/beware-spinlocks-in-user-space/
Dadurch kann man nicht vollständig (Data-)Races verhindern.
-
@firefly sagte in Lesen/Schreiben von Speicher Multithreaded:
Dadurch kann man nicht vollständig (Data-)Races verhindern.
Magst du erklären? In dem verlinkten Beitrag ist von Data Races nicht die Rede.
-
Kann gut sein das mit dem Data Races falsch verstanden habe oder miss interpretiert habe.
-
@manni66 sagte in Lesen/Schreiben von Speicher Multithreaded:
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
daher ist mir die Performance wichtig
Und Spinlocks sind da besser als Mutexes?
Letztendlich wird auch beim Mutex oder bei einer CritSect gewartet. Mag sein, dass das dort effizienter geschieht. Keine Ahnung.
Hier hat scheinbar jemand Performance-Tests mit verschiedenen Lock-Varianten gemacht:
https://stackoverflow.com/questions/13206414/why-slim-reader-writer-exclusive-lock-outperformance-the-shared-oneDas Mutex kommt dabei gar nicht gut weg. Allerdings sind die Umstände des Tests nicht bekannt.
-
@hustbaer sagte in Lesen/Schreiben von Speicher Multithreaded:
Der Trick ist ein einziges Atomic zu verwenden indem man ein Bit für "gibt es einen Writer" und die restlichen Bits für den Leser-Zähler verwendet.
Klingt eigentlich gut, nur es bleibt halt immer das Problem, dass man Abprüfen bis gewünschtem Zustand und Setzen des neuen Zustands ( Z.b. Lese-Zähler erhöht um 1 ) in einem Rutsch erledigen muss, ohne dass ein anderer Thread die Möglichkeit hat zwischen die Lese und die Schreib-Instruktion zu kommen.
-
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
@manni66 sagte in Lesen/Schreiben von Speicher Multithreaded:
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
daher ist mir die Performance wichtig
Und Spinlocks sind da besser als Mutexes?
Letztendlich wird auch beim Mutex oder bei einer CritSect gewartet. Mag sein, dass das dort effizienter geschieht. Keine Ahnung.
Ja klar geht das da effizienter. Deswegen direkt zu Anfang meine Frage. Ein Spinlock ist ja quasi die Nutzung der CPU als Heizung, ein wartender Mutex jedoch gibt die Kontrolle an den OS-Scheduler zurück, verbraucht während der Wartezeit also keine CPU Leistung.
-
@Tyrdal sagte in Lesen/Schreiben von Speicher Multithreaded:
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
@manni66 sagte in Lesen/Schreiben von Speicher Multithreaded:
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
daher ist mir die Performance wichtig
Und Spinlocks sind da besser als Mutexes?
Letztendlich wird auch beim Mutex oder bei einer CritSect gewartet. Mag sein, dass das dort effizienter geschieht. Keine Ahnung.
Ja klar geht das da effizienter. Deswegen direkt zu Anfang meine Frage. Ein Spinlock ist ja quasi die Nutzung der CPU als Heizung, ein wartender Mutex jedoch gibt die Kontrolle an den OS-Scheduler zurück, verbraucht während der Wartezeit also keine CPU Leistung.
Naja letztendlich muss man wissen, was das Ziel ist. Das Mutex braucht deutlich länger, um die Kontrolle zurückzubekommen als z.b. ein Atomic mit Spinlock. Dafür bezahlt man mit CPU-Usage.
Ich werde ohnehin verschiedene Varianten testen, auch mit Mutex und dann mal ein paar Messwerte aufnehmen. Da für mich aber die Zugriffszeiten im Vordergrund stehen ist mir eben an der Lock-Free-Variante mit atomics gelegen. Herausforderungen braucht der Mensch
Das gute am Mutex ist: Wenn es keine Kollision gibt, kann man den Zeitaufwand für Lock und Unlock mit 0 annehmen.
-
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
@hustbaer sagte in Lesen/Schreiben von Speicher Multithreaded:
Der Trick ist ein einziges Atomic zu verwenden indem man ein Bit für "gibt es einen Writer" und die restlichen Bits für den Leser-Zähler verwendet.
Klingt eigentlich gut, nur es bleibt halt immer das Problem, dass man Abprüfen bis gewünschtem Zustand und Setzen des neuen Zustands ( Z.b. Lese-Zähler erhöht um 1 ) in einem Rutsch erledigen muss, ohne dass ein anderer Thread die Möglichkeit hat zwischen die Lese und die Schreib-Instruktion zu kommen.
Allergrundlegendste Grundoperation bei Atomics: Compare-And-Swap.
-
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
Das gute am Mutex ist: Wenn es keine Kollision gibt, kann man den Zeitaufwand für Lock und Unlock mit 0 annehmen.
Du kannst annehmen was du willst, bloss stimmen tut es deswegen nicht. Gerade wenn es keine Kollisionen gibt sind Spin-Locks ideal. Wobei ideal != "0 Kosten". Und der Unterschied zu einer guten Mutex Implementierung ist nicht gross.
-
@It0101 sagte in Lesen/Schreiben von Speicher Multithreaded:
Hier hat scheinbar jemand Performance-Tests mit verschiedenen Lock-Varianten gemacht:
https://stackoverflow.com/questions/13206414/why-slim-reader-writer-exclusive-lock-outperformance-the-shared-oneDas Mutex kommt dabei gar nicht gut weg. Allerdings sind die Umstände des Tests nicht bekannt.
Das was dort "mutex" genannt wird ist wohl ein Windows Mutex: https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexw
Hat mitstd::mutex
nixe zu tun. Wo findet ihr Kids bloss immer diese total sinnfreien Vergleiche?
-
@hustbaer das Internet bietet halt auch viel Schund an. Deswegen schrieb ich ja auch, dass man die Hintergründe der PerformanceMessung nicht kennt, geschweige denn den Quellcode.
Das hier ist wohl vermutlich eine Implementation von dem Lock-Free-Ding, was du erwähnt hast, mit einem INT welches ein WriteAccess-Bit enthält.
https://yizhang82.dev/lock-free-rw-lock
-
@hustbaer sagte in Lesen/Schreiben von Speicher Multithreaded:
Wo findet ihr Kids bloss immer diese total sinnfreien Vergleiche?
Ihr Kids? Ich bin fast vierzig
-
Ich werde jetzt mal verschiedene Variaten implementieren und dann mal 10 Schreib-Threads und 100 Lese-Threads losschicken und die Laufzeit messen. Mal gucken was rauskommt.
-
So ich habe mal eine Messung durchgeführt:
Variante 1: Mutex welches sich Leser und Schreiber teilen, d.h. insgesamt hat immer nur einer Zugriff.
Variante 2: LockFree, diese Implementation hier: https://yizhang82.dev/lock-free-rw-lockSzenario:
- 1 Schreiber
- Variable Anzahl an lesenden Threads ( R=1 -> 64 )
- 5 Mio Zugriffe pro Thread ( jeweils mit lock/unlock), der langsamste Thread bestimmt den Messwert
- Compiler gcc 9.2, getestet auf Centos6 als VM (!!!)
R Mutex LockFree
1 422 896
2 625 993
4 971 2204
8 1901 4019
16 3743 7476
32 7256 14755
64 14255 30840
(Messwerte in millisekunden)
Fazit für mich: Überraschenderweise performt das std::mutex deutlich besser als die LockFree-Variante mit SpinLock...
-
Es gibt auch Mutexe für deine obigen Anforderungen. Also mehrere Leser aber nur ein Schreiber.
Ansonsten wundert mich das Ergebnis nicht.Mutexe wachen auf wenn sie drann sind. Spinlocks spinnen halt vor sich hin und können damit den Thread der den Lock( halt deinen LockFreeLock ) hat von der Ausführung abhalten.
Wenn LockFree automatisch besser wäre würden es alle nur noch so machen.
Ach ja, die Implementierung aus deinem Link kann übrigens die Schreiber verhungern lassen.