2013-05-06 3 views
17

Ich frage mich, wie ein Stück gesperrten Code meinen Code verlangsamen kann, obwohl der Code nie ausgeführt wird. Hier ist ein Beispiel unten:Wie kann man Verlangsamung aufgrund von gesperrtem Code vermeiden?

public void Test_PerformanceUnit() 
{ 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 
    Random r = new Random(); 
    for (int i = 0; i < 10000; i++) 
    { 
     testRand(r); 
    } 
    sw.Stop(); 
    Console.WriteLine(sw.ElapsedTicks); 
} 

public object testRand(Random r) 
{ 
    if (r.Next(1) > 10) 
    { 
     lock(this) { 
      return null; 
     } 
    } 
    return r; 
} 

Dieser Code läuft in ~ 1300ms auf meinem Rechner. Wenn wir den Sperrblock entfernen (aber seinen Körper behalten), erhalten wir 750ms. Fast das Doppelte, obwohl der Code nie ausgeführt wird!

Natürlich tut dieser Code nichts. Ich habe es bemerkt, während ich in einer Klasse, in der der Code überprüft, ob das Objekt initialisiert ist und wenn es nicht initialisiert wird, etwas träge Initialisierung hinzufügt. Das Problem ist, dass die Initialisierung gesperrt ist und alles sogar nach dem ersten Aufruf verlangsamt wird.

Meine Fragen sind:

  1. Warum ist das passiert?
  2. Wie die Verlangsamung zu vermeiden
+0

Wenn Sie nicht vorhaben, "Lock" intensiv zu benutzen - ich würde mich nicht wirklich darum kümmern. – James

+4

Ich bekomme ähnliche Ergebnisse, aber ein Tick ist 100 * Nano * -Sekunden. Beide Läufe sollten ~ 0ms dauern (dh wenn Sie 'sw.ElapseMilliseconds' drucken). Diese" Verlangsamung "(von ~ 0.00006s) ist wahrscheinlich auf die Tatsache zurückzuführen, dass' lock' einen 'try/finally'-Block enthält, was wahrscheinlich der Fall ist Setup, wenn die Methode aufgerufen wird. Versuchen Sie, den Inhalt von "testRand" in die Schleife selbst zu stellen; Sie werden zu diesem Zeitpunkt fast * keine * Verlangsamung sehen. – dlev

+0

Haben Sie versucht, die Methode mit 'AggressiveInline' zu ​​markieren? Vielleicht hat der Sperrcode die Methode für normales Inlining zu groß gemacht. Die .net JITter-Inlines verwenden eine ziemlich dumme Heuristik basierend auf der Größe des IL-Codes. – CodesInChaos

Antwort

9

über warum es passiert, hat es in den Kommentaren diskutiert: es ist aufgrund der Initialisierung der von den lock erzeugt try ... finally.


Und diese Verlangsamung zu vermeiden, können Sie die Sperrfunktion auf ein neues Verfahren extrahieren, so dass der Verriegelungsmechanismus nur dann, wenn das Verfahren tatsächlich aufgerufen wird initialisiert.

Ich versuchte es mit diesem einfachen Code:

public object testRand(Random r) 
{ 
    if (r.Next(1) > 10) 
    { 
     return LockingFeature(); 
    } 
    return r; 
} 

private object LockingFeature() 
{ 
    lock (_lock) 
    { 
     return null; 
    } 
} 

Und hier meine Zeiten sind (in Ticks):

your code, no lock : ~500 
your code, with lock : ~1200 
my code    : ~500 

EDIT: Mein Testcode (etwas langsamer läuft als der Code ohne Sperren) war tatsächlich auf statischen Methoden, es scheint, dass, wenn der Code "innerhalb" eines Objekts ausgeführt wird, die Timings die gleichen sind. Ich habe die Zeit danach festgelegt.

+0

Danke für die Antwort, das war genau das, was ich gesucht habe. In meinem Test lief Ihre Lösung schneller als die 'lock'-Inline, aber langsamer als nur mit' Return null '. Ich habe die Methode 'LockingFeature' als' virtuell' definiert, um Code Inlining zu vermeiden und ich habe 100% meiner Leistung zurückbekommen. – pieroxy

+0

@pieroxy - Eine andere Sache über Ihren ersten Test ist, dass die Version von 'testRand()' mit dem Schloss auch länger zu JIT dauert. Sie können das also aus der Gleichung herausnehmen, indem Sie nur einen Aufruf an 'testRand()' machen, bevor die 'Stoppuhr' beginnt (als eine Art, den JIT-Compiler sozusagen aufzuwärmen). Dies verengt die Lücke erheblich. Dennoch ist Zonkos Code eine ziemlich glatte Art, damit umzugehen. –

Verwandte Themen