c # Sperranweisung Leistung

8

Update - Ich habe die Ursache von lock() CPU-Zyklen essen wie verrückt gefunden, habe diese Information nach meiner ursprünglichen Frage hinzugefügt. Dies alles entpuppte sich als eine Textwand:

TL; DR Der eingebaute c% - Mechanismus lock() verwendet unter bestimmten Umständen eine ungewöhnlich hohe CPU-Zeit, wenn Ihr System mit einem Systemzeitgeber mit hoher Auflösung läuft.

Ursprüngliche Frage:

Ich habe eine Anwendung, die aus mehreren Threads auf eine Ressource zugreift. Die Ressource ist ein Gerät, das an USB angeschlossen ist. Es ist eine einfache Kommando / Antwort-Schnittstelle und ich benutze einen kleinen lock() -Block, um sicherzustellen, dass der Thread, der einen Befehl sendet, auch die Antwort bekommt. Meine Implementierung verwendet das Schlüsselwort lock (obj):

%Vor%

Wenn ich von drei Threads so schnell wie möglich darauf zugreife (in einer engen Schleife), beträgt die CPU-Auslastung auf einem High-End-Computer etwa 24%. Aufgrund der Art des USB-Anschlusses werden nur etwa 1000 Befehls- / Antwortoperationen pro Sekunde ausgeführt. Dann habe ich den hier beschriebenen Sperrmechanismus SimpleExclusiveLock implementiert und der Code sieht nun ähnlich aus (einige versuchen es) / catch stuff, um die Sperre aufzuheben, falls eine E / A-Ausnahme entfernt wurde):

%Vor%

Bei Verwendung dieser Implementierung sinkt die CPU-Auslastung mit demselben 3-Thread-Testprogramm auf & lt; 1%, während immer noch 1000 Befehls / Antwort-Operationen pro Sekunde ausgeführt werden.

Die Frage ist: Was ist in diesem Fall das Problem mit dem integrierten Schlüsselwort lock() ?

Bin ich zufällig auf einen Fall gestoßen, in dem der lock() Mechanismus einen außergewöhnlich hohen Overhead hat? Der Thread, der in den kritischen Bereich eindringt, hält die Sperre nur für ca. 1 ms.

Aktualisierung: Die Ursache von lock() essen CPU wie verrückt ist, dass einige Anwendung die Timer-Auflösung für das gesamte System mit timeBeginPeriod () in winmm.dll erhöht hat. Die Täter in meinem Fall sind Google Chrome und SQL Server - sie forderten eine 1 ms System-Timer-Auflösung mit:

%Vor%

Ich habe das mit dem powercfg-Tool herausgefunden:

%Vor%

Wegen einer Art Konstruktionsfehler in der integrierten lock() -Anweisung isst diese erhöhte Timer-Auflösung CPU wie verrückt (zumindest in meinem Fall). Also habe ich die Programme beendet, die einen Systemtimer mit hoher Auflösung anfordern. Meine Anwendung läuft jetzt etwas langsamer. Jede Anforderung wird jetzt für 16,5 ms statt 1 ms gesperrt. Der Grund dafür ist, dass die Threads weniger häufig geplant werden. Die CPU-Auslastung (wie im Task-Manager angezeigt) ist ebenfalls auf Null gesunken. Ich habe keine Zweifel, dass lock() immer noch einige Zyklen verwendet, aber das ist jetzt versteckt.

In meinem Projekt ist ein niedriger CPU-Verbrauch ein wichtiger Designfaktor. Die niedrige Latenz von 1 ms bei USB-Anfragen ist auch positiv für das Gesamtdesign. Also (in meinem Fall) besteht die Lösung darin, das eingebaute lock() zu verwerfen und durch einen korrekt implementierten Sperrmechanismus zu ersetzen. Ich habe bereits die fehlerhafte System.IO.Ports.SerialPort zugunsten von WinUSB rausgeworfen, also habe ich keine Ängste:)

Ich habe eine kleine Konsolen-Anwendung gemacht, um all das zu demonstrieren, pm mich, wenn Sie an einer Kopie interessiert sind (~ 100 Zeilen Code).

Ich denke, ich habe meine eigene Frage beantwortet, also werde ich das hier lassen, falls jemand interessiert ist ...

    
Mikael 02.04.2014, 08:43
quelle

1 Antwort

5

Nein, das ist nicht möglich. Es gibt kein Szenario, in dem Sie 3 Threads haben, von denen 2 auf der Sperre blockieren und 1 Blockierung bei einer E / A-Operation, die eine Millisekunde dauert, eine 24-prozentige CPU-Auslastung erreichen kann. Der verlinkte Artikel ist vielleicht interessant, aber die .NET Monitor-Klasse macht genau das Gleiche. Einschließen der CompareExchange () - Optimierung und der Warteschlange.

Der einzige Weg zu 24% ist der andere Code, der in Ihrem Programm läuft. Mit dem gewöhnlichen Zyklus-Stealer ist der UI-Thread, den du tausendmal pro Sekunde trommelst. Sehr einfach, Kern auf diese Weise zu brennen. Ein klassischer Fehler, menschliche Augen können nicht so schnell lesen. Mit der weiteren Extrapolation haben Sie dann ein Testprogramm geschrieben, das die Benutzeroberfläche nicht aktualisiert. Und so brennt Kern nicht.

Ein Profiler wird Ihnen natürlich genau sagen, wohin diese Zyklen gehen. Es sollte dein nächster Schritt sein.

    
Hans Passant 02.04.2014 12:11
quelle

Tags und Links