2012-04-11 19 views
8

Chcę uzyskać maksymalną liczbę, jaką muszę wykonać, aby pętla zajęła mu około x milisekund.Zmierz prędkość kodu w .net w milisekundach

Na przykład.

int GetIterationsForExecutionTime(int ms) 
{ 
    int count = 0; 
    /* pseudocode 
    do 
     some code here 
     count++; 
    until executionTime > ms 
    */ 

    return count; 
} 

Jak osiągnąć coś takiego?

+0

'DateTime.Now' rozdzielczość milisekundy (choć nieco inprecise), można uzyskać przybliżone czasy przez' (DateTime.Now - startTime) .TotalMilliseconds'. Jak dokładny musisz być? – mellamokb

+0

Około -100/+ 100 ms odchylenia. –

+10

'DateTime.Now' jest dokładny do około 1/64 sekundy w większości systemów operacyjnych, FYI, co oznacza, że ​​* różnica * dwóch datetimes jest zwykle wyłączona aż o 1/32 sekundy i może być daleko więcej. ** Generalnie lepiej jest użyć Stopwatch w tym celu. ** –

Odpowiedz

28

Chcę, aby uzyskać maksymalną liczbę muszę wykonać pętlę na jej podjęcie x milisekund, aby zakończyć.

Po pierwsze, nie rób tego. Jeśli musisz poczekać określoną liczbę milisekund , nie pracuj-czekaj w pętli. Zamiast tego, uruchom zegar i wróć. Kiedy zegar tyka, niech wywoła metodę, która wznowi się w miejscu, w którym przerwałeś. Metoda Task.Delay może być dobra do użycia; Dba o szczegóły timera.

Jeśli rzeczywiście masz pytanie, jak wydłużyć czas potrzebny na wykonanie kodu, potrzebujesz znacznie więcej niż tylko dobrego timera. Jest dużo sztuki i nauki, aby uzyskać dokładne czasy.

Najpierw należy zawsze używać Stopwatch i nigdy nie używać DateTime.Now dla tych czasów. Stoper został zaprojektowany tak, aby był precyzyjnym timerem, informującym o tym, ile czasu upłynęło. DateTime.Now to precyzyjny czasomierz, który informuje Cię o , jeśli nadszedł czas, aby obejrzeć Doctor Who jeszcze. Nie używałbyś zegara ściennego do czasu wyścigu olimpijskiego; używałbyś najwyższej precyzji stopera, na który można wpaść. Więc użyj tego, który dla ciebie przewidziano.

Po drugie, należy pamiętać, że kod C# jest kompilowany Just In Time. Pierwszy raz, gdy przechodzisz przez pętlę, może być setki lub tysiące razy droższy niż każdy kolejny czas ze względu na koszt jittera analizujący kod, który wywołuje pętla. Jeśli zamierzasz mierzyć "ciepły" koszt pętli, musisz uruchomić pętlę raz przed możesz rozpocząć odmierzanie czasu. Jeśli zamierzasz zmierzyć średnio koszt , w tym czas jit, musisz zdecydować, ile razy tworzy uzasadnioną liczbę prób, tak aby średnia wyszła poprawnie.

Po trzecie, musisz upewnić się, że nie nosisz ciężaru ołowiu podczas pracy. Nigdy nie wykonuj pomiarów wydajności podczas debugowania. Zadziwiająca liczba osób, które to robią. Jeśli jesteś w debugerze, to środowisko wykonawcze może być rozmawiające z debuggerem, aby upewnić się, że otrzymujesz doświadczenie debugowania, które chcesz, i że rozmowy wymagają czasu. Jitter generuje gorszy kod niższy niż zwykle, aby twoje doświadczenie w debugowaniu było bardziej spójne. Śmieciarz jest zbierając mniej agresywnie. I tak dalej. Zawsze uruchamiaj pomiary wydajności poza debuggerem i włączonymi optymalizacjami.

Po czwarte, pamiętaj, że systemy pamięci wirtualnej w systemach nakładają koszty podobne do tych z wahań. Jeśli już korzystasz z zarządzanego programu lub ostatnio go uruchomiłeś, to strony wymaganego CLR są prawdopodobnie "gorące" - już w pamięci RAM - gdzie są szybkie. Jeśli nie, strony mogą być zimne, na dysku i muszą być uszkodzone przez stronę. To może ogromnie zmienić czas.

Po piąte, pamiętaj, że jitter może dokonywać optymalizacji, których nie spodziewasz się uzyskać.. Jeśli spróbujesz raz:

// Let's time addition! 
for (int i = 0; i < 1000000; ++i) { int j = i + 1; } 

jitter jest całkowicie wewnątrz swoich praw, aby usunąć całą pętlę. Może zdać sobie sprawę, że pętla nie oblicza wartości używanej nigdzie indziej w programie i całkowicie ją usunąć, co daje czas zero. Czy to robi? Może. Może nie. To zależy od jittera. Powinieneś zmierzyć wydajność realistycznego kodu , gdzie obliczone wartości są w jakiś sposób używane; jitter będzie wiedział, że nie może ich zoptymalizować.

Po szóste, czasy testów, które powodują powstawanie dużej ilości śmieci, mogą zostać odrzucone przez śmieciarz. Załóżmy, że masz dwa testy, jeden, który sprawia, że ​​dużo śmieci i który sprawia, że ​​trochę. Koszt zebrania śmieci wyprodukowanych w pierwszym teście można "obciążyć" czasem potrzebnym do uruchomienia drugiego testu, jeśli na szczęście pierwszy test udaje się uruchomić bez kolekcji, ale drugi test uruchamia jeden. Jeśli twoje testy przynoszą dużo śmieci, to rozważ (1), czy mój test jest realistyczny na początek? Nie ma sensu wykonywanie pomiaru wydajności nierealistycznego programu, ponieważ nie można wyciągnąć dobrych wniosków, jak zachowa się twój prawdziwy program. I (2) czy powinienem naliczać koszty zbiórki śmieci na próbę, która wytworzyła śmieci? Jeśli tak, upewnij się, że wymusisz pełną kolekcję przed upływem czasu testu.

Po siódme, kod jest uruchamiany w środowisku wielowątkowym, wieloprocesorowym, gdzie wątki mogą być przełączane do woli, i gdzie kwantowa wątek (ilość czasu, przez którą system operacyjny da inny wątek, aż twoja szansa się uruchomi ponownie) wynosi około 16 milisekund. 16 milisekund to około pięćdziesiąt milionów cykli procesora. Dochodzenie z dokładnymi czasami operacji w zakresie poniżej milisekund może być dość trudne, jeśli przełączenie wątku ma miejsce w jednym z kilku milionów cykli procesora, które próbujesz zmierzyć. Weź to pod uwagę.

+1

Odnośnie do punktu trzeciego: Robiłem nawyk przyklejenia 'if (Debugger.IsAttached) {Console.WriteLine (" Dołączony jest Debugger, testowy test nieważny (ty głupku)! "); } 'w moich testach porównawczych. – ligos

6

Można również użyć klasy Stopwatch:

int GetIterationsForExecutionTime(int ms) 
{ 
    int count = 0; 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start();   
    do 
    { 
     // some code here 
     count++; 
    } while (stopwatch.ElapsedMilliseconds < ms); 

    stopwatch.Stop(); 
    return count; 
} 
9
var sw = new Stopwatch(); 
sw.Start(); 
... 
long elapsedMilliseconds = sw.ElapsedMilliseconds; 
+0

czy nie musisz przestać sw? :) –

+3

Nie, możesz przeczytać tę nieruchomość w dowolnym momencie. – djdanlib

-1

Dobre punkty od Eric Lippert. Przez pewien czas testowałem benchmarking i testy jednostkowe i radziłbym, abyś odrzucił wszystkie pierwsze zdania na temat kodu powodującego kompilację JIT. Więc w kodzie benchmarkingu które wykorzystują pętlę i Stoper pamiętać, aby umieścić to na koniec pętli:

   // JIT optimization. 
       if (i == 0) 
       { 
        // Discard every result you've collected. 
        // And restart the timer. 
        stopwatch.Restart(); 
       }