Ich schreibe ein Thread-sicheres Objekt, das im Grunde genommen ein Double darstellt und eine Sperre verwendet, um sicheres Lesen und Schreiben zu gewährleisten. Ich benutze viele dieser Objekte (20-30) in einem Stück Code, der sie alle 100 Mal pro Sekunde liest und schreibt, und ich bemesse die durchschnittliche Rechenzeit jedes dieser Zeitschritte. Ich fing an, einige Optionen für Implementierungen meines Getters zu betrachten und nachdem ich viele Tests durchgeführt und viele Samples gesammelt hatte, um die Berechnung der Rechenzeit zu mitteln, waren bestimmte Implementierungen konsistent besser als andere, aber nicht die Implementierungen, die ich erwarten würde >
Implementierung 1) Berechnungszeit Durchschnitt = 0,607 ms:
%Vor%Implementierung 2) Rechenzeitmittelwert = 0,615 ms:
%Vor%Implementierung 3) Rechenzeitmittelwert = 0,560 ms:
%Vor%Was ich erwartet hatte: Ich hatte erwartet, dass Implementierung 3 die schlechteste der drei sein würde (das war eigentlich mein ursprünglicher Code, also war es zufälliges oder faules Coding, dass ich es so geschrieben hatte) , aber überraschend ist es immer das beste in Bezug auf die Leistung. Ich würde erwarten, dass Implementierung 1 die schnellste ist. Ich habe auch erwartet, dass die Implementierung 2 mindestens genauso schnell ist, wenn nicht schneller als die Implementierung 3, da ich gerade eine Zuweisung zu dem doppelten Ergebnis lösche, das sowieso überschrieben wird, so dass es unnötig ist.
Meine Frage ist: kann jemand erklären, warum diese drei Implementierungen die relative Leistung haben, die ich gemessen habe? Es scheint mir kontraintuitiv zu sein und ich würde gerne wissen warum.
Ich stelle fest, dass diese Unterschiede nicht groß sind, aber ihr relatives Maß ist jedes Mal konsistent, wenn ich den Test durchführe, und sammle Tausende von Stichproben bei jedem Test, um die Rechenzeit zu mitteln. Bitte beachten Sie auch, dass ich diese Tests mache, weil meine Anwendung sehr hohe Leistung erfordert, oder zumindest so gut, wie ich es vernünftigerweise bekommen kann. Mein Testfall ist nur ein kleiner Testfall und die Leistung meines Codes wird wichtig sein, wenn er in der Veröffentlichung läuft.
BEARBEITEN: Beachten Sie, dass ich MonoTouch benutze und den Code auf einem iPad Mini-Gerät laufe, damit es nichts mit c # und mehr zu tun hat, was mit dem Cross-Compiler von MonoTouch zusammenhängt.
Das Messen nur von Lesevorgängen für Nebenläufigkeit ist irreführend, Ihr Cache wird Ihnen um Größenordnungen bessere Ergebnisse liefern als der reale Anwendungsfall. Also habe ich SetValue zu Marc's Beispiel hinzugefügt:
%Vor%Ergebnisse sind, bei 1000: 1 gelesen: Schreib-Verhältnis:
%Vor%100: 1 (lesen: schreiben)
%Vor%10: 1 (lesen: schreiben)
%Vor%2: 1 (lesen: schreiben)
%Vor%1: 1 (lesen: schreiben)
%Vor%* Der Code wurde aktualisiert, um unnötige ICX-Operationen beim Schreiben zu entfernen, da der Wert immer überschrieben wird. Außerdem wurde die Formel so festgelegt, dass die Anzahl der Lesevorgänge berechnet wird, die nach Threads aufgeteilt werden sollen (gleiche Arbeit).
Ehrlich gesagt gibt es hier andere, bessere Ansätze. Die folgenden Ausgaben (ignorieren die x1, die für JIT ist):
%Vor% All dies sind thread-sichere Implementierungen. Wie Sie sehen können, ist das schnellste eine typisierte Box gefolgt von einer untypisierten ( object
) Box. Next kommt (ungefähr in der gleichen Geschwindigkeit) Interlocked.CompareExchange
/ Interlocked.Read
- Beachten Sie, dass letzteres nur long
unterstützt, also müssen wir etwas Bit-Bashing machen, um das als double
zu behandeln.
Offensichtlich testen Sie Ihr Zielframework.
Zum Spaß habe ich auch ein Mutex
getestet; im gleichen Maßstab Test, das dauert etwa 3300ms.
Tags und Links c# performance locking