2013-05-06 8 views
17

Zastanawiam się, jak kawałek zablokowanego kodu może spowolnić mój kod, mimo że kod nigdy nie jest wykonywany. Oto przykład poniżej:Jak uniknąć spowolnienia z powodu zablokowanego kodu?

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; 
} 

Ten kod działa w ~ 1300ms na moim komputerze. Jeśli usuniemy blokadę (ale zachowamy jej ciało), otrzymamy 750ms. Prawie podwójne, mimo że kod nigdy nie działa!

Oczywiście ten kod nic nie robi. Zauważyłem to podczas dodawania leniwego inicjowania w klasie, w której kod sprawdza, czy obiekt jest inicjowany i jeśli nie inicjuje go. Problem polega na tym, że inicjalizacja jest zablokowana i spowalnia wszystko, nawet po pierwszym wywołaniu.

Moje pytania są następujące:

  1. Dlaczego tak się dzieje?
  2. Jak uniknąć spowolnienia
+0

Jeśli nie zamierzasz używać "blokady" intensywnie - nie martwiłbym się tym. – James

+4

Otrzymuję podobne wyniki, ale tyknięcie to 100 * nano * -seconds. Oba przebiegi powinny zająć ~ 0ms (tzn. Jeśli wypiszesz 'sw.ElapseMilliseconds'.) To" spowolnienie "(z ~ 0.00006s) jest prawdopodobnie spowodowane tym, że' lock' zawiera blok 'try/finally', który prawdopodobnie jest konfiguracja po wywołaniu metody. Spróbuj umieścić zawartość 'testRand' w pętli; w tym momencie widać prawie * nie * spowolnienie. – dlev

+0

Czy próbowałeś zaznaczyć metodę za pomocą 'AggressiveInline'? Być może kod blokujący spowodował, że metoda była zbyt duża dla normalnego wstawiania. JITter .net inline używa raczej głupiego heurystyki opartej na rozmiarze kodu IL. – CodesInChaos

Odpowiedz

9

o tym, dlaczego to się dzieje, to zostało omówione w komentarzach: to dzięki inicjalizacji try ... finally generowanego przez lock.


I uniknąć tego spowolnienia, można wyodrębnić element blokujący do nowej metody, tak, że mechanizm blokujący zostanie zainicjowany tylko jeśli metoda jest faktycznie nazywa.

próbowałem go z tego prostego kodu:

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

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

A oto moje czasy (w kleszcze):

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

EDIT: Moje kodu testowego (działa nieco wolniej niż kod bez blokad) był faktycznie na statycznych metod, wydaje się, że gdy kod jest uruchomiony "wewnątrz" obiektu, czasy są takie same. Ustaliłem czas zgodnie z tym.

+0

Dzięki za odpowiedź, właśnie tego szukałem. W moim teście twoje rozwiązanie działało szybciej niż 'lock' w linii, ale wolniej niż po' return null'. Zdefiniowałem metodę 'LockingFeature' jako' virtual', aby uniknąć wstawiania kodu i otrzymałem 100% mojej wydajności. – pieroxy

+0

@pieroxy - Kolejną rzeczą w twoim pierwszym teście jest to, że wersja 'testRand()' z blokadą trwa dłużej również dla JIT. Możesz więc usunąć to z równania, wykonując tylko jedno wywołanie funkcji 'testRand()' zanim rozpocznie się 'Stopwatch' (jako sposób na rozgrzanie kompilatora JIT, że tak powiem). To znacznie zmniejsza lukę. Jednak kod Zonko to całkiem sprytny sposób radzenia sobie z tym. –

Powiązane problemy