Jak piszesz (i uruchamiasz) poprawny mikro-test w Javie?
Szukam przykładów kodu i komentarzy ilustrujących różne rzeczy do przemyślenia.
Przykład: Czy test porównawczy powinien mierzyć czas / iterację lub iteracje / czas i dlaczego?
Powiązane: Czy testowanie stopera jest dopuszczalne?
java
jvm
benchmarking
jvm-hotspot
microbenchmark
John Nilsson
źródło
źródło
Odpowiedzi:
Wskazówki dotyczące pisania mikroprocesorów od twórców Java HotSpot :
Zasada 0: Przeczytaj renomowany artykuł na temat maszyn JVM i mikro-benchmarkingu. Dobrym jest Brian Goetz, 2005 . Nie oczekuj zbyt wiele od mikro-testów; mierzą tylko ograniczony zakres charakterystyki wydajności JVM.
Reguła 1: Zawsze dołączaj fazę rozgrzewki, która uruchamia jądro testowe przez cały czas, wystarczającą do uruchomienia wszystkich inicjalizacji i kompilacji przed fazami czasowymi. (Mniej iteracji jest w porządku w fazie rozgrzewania. Zasadą jest kilkadziesiąt tysięcy iteracji w wewnętrznej pętli).
Zasada nr 2: Zawsze uruchamiaj z
-XX:+PrintCompilation
,-verbose:gc
itd, więc można sprawdzić, że kompilator i inne części JVM nie robią nieoczekiwany prace podczas fazy rozrządu.Reguła 2.1: Drukuj wiadomości na początku i na końcu faz synchronizacji i rozgrzewki, abyś mógł sprawdzić, czy nie ma wyjścia z reguły 2 podczas fazy synchronizacji.
Zasada 3: Bądź świadomy różnicy między
-client
i-server
, a OSR i regularnymi kompilacjami.-XX:+PrintCompilation
Flaga informuje OSR składanki z co-znak oznaczający zakaz początkowy punkt wejścia, npTrouble$1::run @ 2 (41 bytes)
. Preferuj serwer od klienta, a normalnie OSR, jeśli zależy Ci na najlepszej wydajności.Zasada 4: Bądź świadomy efektów inicjalizacji. Nie drukuj po raz pierwszy podczas fazy pomiaru czasu, ponieważ drukowanie ładuje i inicjuje klasy. Nie ładuj nowych klas poza fazą rozgrzewki (lub końcową fazą raportowania), chyba że testujesz ładowanie klas konkretnie (iw takim przypadku ładuj tylko klasy testowe). Zasada 2 jest pierwszą linią obrony przed takimi efektami.
Zasada 5: Bądź świadomy efektów deoptimizacji i rekompilacji. Nie bierz żadnej ścieżki kodu po raz pierwszy w fazie synchronizacji, ponieważ kompilator może śmieci i ponownie skompilować kod, w oparciu o wcześniejsze optymistyczne założenie, że ścieżka nie będzie w ogóle używana. Zasada 2 jest pierwszą linią obrony przed takimi efektami.
Reguła 6: Użyj odpowiednich narzędzi, aby odczytać zdanie kompilatora i spodziewaj się zaskoczenia kodem, który produkuje. Sprawdź sam kod przed sformułowaniem teorii na temat tego, co czyni coś szybszym lub wolniejszym.
Zasada 7: Zmniejsz hałas w pomiarach. Uruchom test porównawczy na cichej maszynie i uruchom go kilka razy, odrzucając wartości odstające. Użyj
-Xbatch
do serializacji kompilatora z aplikacją i rozważ ustawienie,-XX:CICompilerCount=1
aby uniemożliwić równoległe działanie kompilatora. Postaraj się zmniejszyć obciążenie GC, ustawXmx
(wystarczająco duży) równośćXms
i użyj,UseEpsilonGC
jeśli jest dostępny.Zasada 8: Użyj biblioteki do testu porównawczego, ponieważ jest ona prawdopodobnie bardziej wydajna i została już debugowana wyłącznie w tym celu. Takich jak JMH , Caliper lub Bill and Paul's Excellent UCSD Benchmarks for Java .
źródło
System.nanoTime()
nie ma gwarancji, że będzie dokładniejszy niżSystem.currentTimeMillis()
. Jest gwarantowana tylko co najmniej tak dokładna. Zwykle jest jednak znacznie dokładniejszy.System.nanoTime()
zamiast tegoSystem.currentTimeMillis()
jest to, że ten pierwszy gwarantuje monotoniczny wzrost. Po odjęciu wartości zwrócone dwacurrentTimeMillis
wywołania mogą faktycznie dać negatywne wyniki, być może dlatego, że czas systemowy został dostosowany przez jakiegoś demona NTP.Wiem, że to pytanie zostało oznaczone jako odpowiedź, ale chciałem wspomnieć o dwóch bibliotekach, które pomagają nam pisać mikroskopy
Suwmiarka od Google
Wprowadzenie do samouczków
JMH z OpenJDK
Wprowadzenie do samouczków
źródło
Ważnymi rzeczami dla testów porównawczych Java są:
System.gc()
między iteracjami, dobrym pomysłem jest uruchomienie go między testami, aby mieć nadzieję, że każdy test uzyska „czystą” przestrzeń pamięci do pracy. (Tak,gc()
to raczej podpowiedź niż gwarancja, ale bardzo prawdopodobne jest , że naprawdę zbierze śmieci w moim doświadczeniu.)Właśnie bloguję na temat projektowania frameworka w .NET. Mam kilka z wcześniejszych postów , które mogą być w stanie dać ci kilka pomysłów - nie wszystko będzie właściwe, oczywiście, ale niektóre z nich mogą być.
źródło
gc
zawsze zwalnia nieużywaną pamięć.System.gc()
, jak możesz zminimalizować zbieranie śmieci w jednym teście ze względu na obiekty utworzone w poprzednich testach? Jestem pragmatyczny, nie dogmatyczny.jmh jest najnowszym dodatkiem do OpenJDK i został napisany przez niektórych inżynierów wydajności z Oracle. Z pewnością warte obejrzenia.
Bardzo ciekawe informacje zakopane w komentarzach do przykładowych testów .
Zobacz też:
źródło
To zależy od tego , co próbujesz przetestować.
Jeśli jesteś zainteresowany opóźnieniami , użyj czasu / iteracji, a jeśli jesteś zainteresowany przepustowością , użyj iteracji / czasu.
źródło
Jeśli próbujesz porównać dwa algorytmy, wykonaj co najmniej dwa testy porównawcze dla każdego z nich, zmieniając kolejność. to znaczy:
Zauważyłem pewne zauważalne różnice (czasami 5-10%) w czasie wykonywania tego samego algorytmu w różnych przebiegach.
Upewnij się również, że n jest bardzo duże, aby czas działania każdej pętli wynosił co najmniej 10 sekund. Im więcej iteracji, tym bardziej znaczące dane w czasie testu i tym bardziej wiarygodne są dane.
źródło
Upewnij się, że w jakiś sposób korzystasz z wyników obliczonych w kodzie testowym. W przeciwnym razie kod można zoptymalizować.
źródło
Istnieje wiele możliwych pułapek pisania mikrokredytów w Javie.
Po pierwsze: musisz obliczyć za pomocą wszelkiego rodzaju zdarzeń, które zajmują mniej więcej czas: zbieranie śmieci, efekty buforowania (systemu operacyjnego dla plików i procesora dla pamięci), operacji we / wy itp.
Po drugie: nie można ufać dokładności zmierzonych czasów w bardzo krótkich odstępach czasu.
Po trzecie: JVM optymalizuje kod podczas wykonywania. Tak więc różne przebiegi w tej samej instancji JVM będą coraz szybsze.
Moje rekomendacje: Spraw, by Twój test porównawczy działał przez kilka sekund, co jest bardziej niezawodne niż czas działania przez milisekundy. Rozgrzej JVM (oznacza uruchomienie testu co najmniej raz bez mierzenia, że JVM może uruchomić optymalizacje). I przeprowadź test porównawczy wiele razy (może 5 razy) i weź medianę. Uruchom każdy mikro-test porównawczy w nowej instancji JVM (wywołaj każdą nową testową Javę), w przeciwnym razie efekty optymalizacji JVM mogą wpłynąć na późniejsze testy. Nie wykonuj rzeczy, które nie są wykonywane w fazie rozgrzewania (ponieważ może to spowodować obciążenie klasy i rekompilację).
źródło
Należy również zauważyć, że ważna może być również analiza wyników mikroprocesora przy porównywaniu różnych wdrożeń. Dlatego test istotności należy wykonać .
Wynika to z faktu, że wdrożenie
A
może być szybsze przez większość przebiegów testu porównawczego niż wdrożenieB
. AleA
może również mieć większy spread, więc zmierzona korzyść z wydajnościA
nie będzie miała żadnego znaczenia w porównaniu zB
.Ważne jest więc, aby poprawnie napisać i uruchomić mikroprocesor, ale także poprawnie go przeanalizować.
źródło
Aby dodać do innych doskonałych porad, będę również pamiętać o następujących kwestiach:
W przypadku niektórych procesorów (np. Seria Intel Core i5 z TurboBoost) temperatura (i liczba obecnie używanych rdzeni, a także ich procent wykorzystania) wpływa na szybkość zegara. Ponieważ procesory są taktowane dynamicznie, może to wpłynąć na wyniki. Na przykład, jeśli masz aplikację jednowątkową, maksymalna prędkość zegara (z TurboBoost) jest wyższa niż dla aplikacji wykorzystującej wszystkie rdzenie. Może to zatem zakłócać porównania wydajności jedno- i wielowątkowej w niektórych systemach. Należy pamiętać, że temperatura i napięcia mają również wpływ na to, jak długo utrzymywana jest częstotliwość turbo.
Być może bardziej fundamentalny aspekt, nad którym masz bezpośrednią kontrolę: upewnij się, że mierzysz właściwą rzecz! Na przykład, jeśli używasz
System.nanoTime()
do testowania określonego fragmentu kodu, umieść wywołania zadania w miejscach, które mają sens, aby uniknąć mierzenia rzeczy, które Cię nie interesują. Na przykład nie rób:Problem polega na tym, że nie kończy się od razu, kiedy kod się skończy. Zamiast tego spróbuj wykonać następujące czynności:
źródło
println
, a nie osobna linia nagłówka lub coś takiego, iSystem.nanoTime()
należy to ocenić jako pierwszy krok w konstruowaniu ciągu arg dla tego wywołania. Kompilator nie może nic zrobić z pierwszym, czego nie może zrobić z drugim, i żaden z nich nawet nie zachęca ich do dodatkowej pracy przed zarejestrowaniem czasu zatrzymania.http://opt.sourceforge.net/ Java Micro Benchmark - zadania kontrolne wymagane do określenia porównawczych charakterystyk wydajności systemu komputerowego na różnych platformach. Może służyć do kierowania decyzjami dotyczącymi optymalizacji i porównywania różnych implementacji Java.
źródło