wtorek, 11 listopada 2014

Introduction to increase performance application based on JVM - part 1 - basic monitoring

Każdy programista chce aby jego produkt działał wydajnie i szybko. Wyobrażenie wydajniej aplikacji przyczyniło się w moim przypadku, iż na studiach uważałem, że język C++ jest jedynym właściwym językiem do budowy szybkich systemów czy aplikacji. Po pewnym czasie zobaczyłem ile trzeba się napierd...ć z tymi wskaźnikami, obsługą pamięci itd. To dla mnie był koszmar. Wtedy zorientowałem się , że w Javie jest JVM, który to większość problemów znanych mi z C rozwiązuje. Mało tego okazało się, że ogłoszeń o pracę jest dużo, dużo więcej i to był już gwóźdź do trumny mojej przygody z C czy C++.





Ale po kilku latach pracy z Javą okazało się, że znajomość działania GC czy samego JVM jest ważna i pozwala uwolnić więcej i więcej mocy z języków opartych na JVM.

 Wstęp do skalowania i profilowania omówiony był w tym poście.

Symptomy świadczące o problemach wydajnościowych to : 
 - wolne działanie aplikacji
 - aplikacja spowalnia z czasem lub po jakimś czasie występuje całkowity crash
 - wycieki pamięci wolne - powodujące spadek wydajności
 - wycieki pamięci na tyle duże, że powodują awarię systemu
 - czasowe skoki procesora lub zamrożenie działania aplikacji
 - problemy zdarzające się na systemach produkcyjnych nie mają swoich identyfikacji w środowisku testowym


Na co zwracać uwagę  :
 - staraj się pisać lub poprawiać algorytmy - tu z pomocą przyjdzie mechanizm microbenchmark'ów (JMH w następnych postach)

 - pisz mniej kodu
   Prawda jest taka, że mała ilość dobrze napisanego kodu będzie odpalała się szybciej niż duża ilość kodu tego samego standardu.
 Ponieważ :
  - czas kompilacja trwa dłużej - ma to wpływ na szybsze uruchomienie
  - alokacja pamięci i następnie cykle GC będą trwały mimowolnie dłużej dla większej liczby obiektów
  - ładowanie klas z dysku do JVM - opóźni start programu
  - więcej kodu to bardziej zapchany cache sprzętowy
  - dodanie każdego małego feature wpływa pośrednio na wydajność aplikacji
  Jeśli jakaś funkcjonalność nie jest potrzeba nie twórz jej a zbliżasz się jednocześnie do podejścia AGILE w wytwarzaniu softu
  - używaj lepszych i szybszych  bibliotek np zamień log4j na logback
    jackson mapper na fasterXml itd

 - baza danych prawie zawsze jest wąskim gardłem - pamiętaj o użyciu cache

 - użycie mechanizmy Stream z javy 8 pozwala na wykorzystanie wszystkich dostępnych rdzenie pocesora

  - monitoring logów JVM pozwala na analizę i eliminację przestojów GC

  - synchronizację i współdzielenie zasobów :
     -  immutable
     - zakładanie lock'ow na możliwie małym kawałku kodu
     - użycie kolekcji i mechnizmów z java.util.concurrent
    - Atomic... czyli mechanizm compare and swap z sun.misc.Unsafe
       (np unsafe.compareAndSwap)
   - zastosowanie operacji NIO nieblokujących - zamiast starych  mechanizmów I/O

  - używać dobrych praktyk - Effective Java czy Clean Code to podstawa

Twoja reakcja powinna iść w kierunku : monitoring -> profilowanie -> dostrajanie

Monitoring to bezinwazyjne zbieranie statystyk z środowisk produkcyjnych, przejściowych czy programistycznych. Najczęściej przyjmować będzie ono postać JMX bean'ów.

Profilowanie nastąpi już na wyraźną potrzebę zmian czy problemu zaobserwowanego w czasie fazy monitorowania czy testów wydajnościowo/obciężeniowych

Dostrajanie - na podstawie zebranych statystyk systemu oraz aplikacji czy kontenera na którym osadzona jest aplikacja podjęcie działania w celu eliminacji problemu i wypełnienia warunków uzgodnionych w SLA.


Wyróżniamy taki oto podział od samej góry kierując się do dołu : 
    - aplikacja J2EE
    - serwer aplikacyjny/kontener serwletów
    - JVM
    - OS
    - sprzęt (hardware)

Jeśli zaczynasz pracę nad aplikacją, która to wymaga restrykcyjnych wymagać wydajnościowych to zastanów się nad : 
- określeniem celi (SLA , parametry  aplikacji)
- przepustowością swojej aplikacji
- czasem odpowiedzi
- możliwością skalowania
- opóźnieniem pomiędzy żądaniem a jakimkolwiek reakcją na takie żądanie (max i min)
- liczbą użytkowników którzy będą jednocześnie korzystać z systemu
- czy częstotliwość oczyszczania pamięci ma wpływ na opóźnienie













Zastanów się również nad kompromisem między :
- czasem opóźnienia a przepustowością
- skalowalnością a wydajnością
- spójnością a dostępnością

Bardzo fajny przykład czytałem parę lat temu na jakimś amerykańskim blogu.
Chodziło o transport ale nie było powiedziane czego z Chicago do New York.
Jeśli chodziłoby o osobę wybrałbyś pewnie samolot (latency) , ale jeśli w grę wchodziłoby kilka ton węgla - wtedy pociąg byłby idealny (throughput)


Istnieją dwa podejścia do optymalizacji systemu : 

Top down  - sprawdzamy coraz niższe warstwy aplikacji w poszukiwaniu problemów oraz potencjalnych wąskich gardeł
(Stosowana przez developerów)
Testujemy aplikacją aby zbadać pod jakimś założonym obciążeniem pojawiają się problemy z wydajnością lub określić próg stabilnej i akceptowalnej pracy aplikacji w zgodności z SLA


Monitorujemy :
 - statystyki systemu operacyjnego
 - statystyki JVM
 - statystyki kontenera aplikacji

Na podstawie powyższych danych możemy przeprowadzić analizę wyników oraz regulację mechanizmu odzyskiwania pamięci.


Buttom up - zaczynamy od najniższej warstwy takiej jak zasoby sprzętowe : procesor, pamięć itd i idziemy wyżej
(Stosowana przez specjalistów od spraw wydajności , różne platformy , systemy operacyjne)

Poprawiamy wydajność aplikacji w stosunku do danej platformy czy systemu operacyjnego.
Kiedy nie ma możliwości przeprowadzenia zmian w kodzie źródłowym

Statystyki które  nas interesują to :
 - liczba instrukcji CPU niezbędnych do wykonania danego zadania (path lenght)
 - liczbę odwołań poza pamięcią cache (mniejsza liczba odwołań to większa wydajność, bo okres pobierania danych będzie krótszy)


optymalizacja obejmuje :
 - zmiana struktur danych - w tym samym bloku pamięci podręcznej
 - zmiana algorytmu szeregowania instrukcji CPU
 - optymalizacja magazynów danych, modyfikacji sprzętu, protokołów


Modyfikacje obejmują w tej metodzie głównie usprawnienie wykorzystania CPU.
Zmiana konstrukcji w kodzie źródłowym poprzez umieszczenie często używanych danych obok siebie może implikować zmniejszeniem czasu pobierania liczby odwołań poza CPU cache. Tutaj do akcji wkroczy często JIT

TIP : JIT - generacja bardziej efektywnego kodu w oparciu o zaistniałe warunki
(o JIT w następnych postach)


 CPU może być wykorzystywany przez :
  - użytkownika (user) -  czas w % jaki aplikacja spędza na wykonywaniu kodu aplikacji
  - jądro (kernel) - czas w % jaki aplikacja spędza na wykonanie kodu jądra
  - system (sys) - czas w % jaki aplikacja spędza na wykonanie dyrektyw systemu

Wysoka zajętość % czasu przez kernel lub sys może wskazywać na konflikty wejścia/wyjścia lub zbyt długą iteracje między tymi urządzeniami
Należy dążyć aby minimalizować ten czas gdyż może być on z powodzeniem wykorzystany na przetwarzanie kodu aplikacji.

IPC - (instructions per clock) - liczba instrukcji CPU przypadającej na cykl zegara
CPI - (cycle per instruction) - liczba cykli zegara przypadających na instrukcje

Oczekiwanie procesora spowodowane pobieraniem danych z pamięci podręcznej  określamy jako przestój (stall). Przestój występuje wtedy kiedy procesor wykonuje jakąś instrukcję i a dane, które są potrzebne do zakończenia jej nie są dostępne w rejestrze lub pamięci podręcznej procesora.
Marnujemy więc cykle na doładowanie danych, które to z punktu widzenia aplikacji powinny być już dostępne.



Monitorujemy CPU za pomocą programów :
 - System monitor
 - htop
 - top
 - vmstat vmstat [interval] [count]

  • Procs – r: Total number of processes waiting to run
  • Procs – b: Total number of busy processes
  • Memory – swpd: Used virtual memory
  • Memory – free: Free virtual memory
  • Memory – buff: Memory used as buffers
  • Memory – cache: Memory used as cache.
  • Swap – si: Memory swapped from disk (for every second)
  • Swap – so: Memory swapped to disk (for every second)
  • IO – bi: Blocks in. i.e blocks received from device (for every second)
  • IO – bo: Blocks out. i.e blocks sent to the device (for every second)
  • System – in: Interrupts per second
  • System – cs: Context switches
  • CPU – us, sy, id, wa, st: CPU user time, system time, idle time, wait time



vmstat -D   -> show system disk statistics
CPU Scheduler Run Queue - powstaje gdy jakieś lekkie procesy gotowe do uruchomienia czekają na swoje kolej. Jeśli jest więcej takich procesów niż procesorów w systemie takie procesy trafiają do kolejki oczekiwania na swoją kolej. Idealna sytuacja jest wtedy kiedy takich procesów jest mniej lub równo z procesorami wirtualnymi maszyny. Przyjmuje się, że czterokrotność liczby procesów w stosunku do procesorów może powodować widoczne spowolnienie systemu.
Głębokość kolejki wskazuje zatem na obciążenie systemu zadaniami

Skoro jesteśmy przy tym to należy zwrócić też uwagę, że zwiększenie liczby wątków czasem może przenieść odwrotny efekt niż zwiększenie wydajności systemu.
Najlepiej jak w przypadku mechanizmu Javy 8 Stream , który w przetwarzaniu równoległym tworzy tyle wątków ile procesów wirtualnych.

W przeciwnym razie system musi obsłużyć jakoś te wątki. Następują wtedy przełączenia kontekstu ma to wpływ na :
 - czyszczenie TLB 
 - zabrudzenia lub czyszczenie caches w procesorze

Przełączenia kontekstu sprawdzamy dzięki : 
cs - przełączanie kontekstu
Dobrowolne przełączanie kontekstu jest wtedy jak wykonywany wątek  dobrowolnie zwalnia CPU

 Koszt przełączenia kontekstu :
 źródło : how-long-does-it-take-to-make-context.html
  • Intel 5150: ~4300ns/context switch
  • Intel E5440: ~3600ns/context switch
  • Intel E5520: ~4500ns/context switch
  • Intel X5550: ~3000ns/context switch
  • Intel L5630: ~3000ns/context switch
  • Intel E5-2620: ~3000ns/context switch

si - ilość pamięci na stronach wchodzących
so - ilość pamięci na stronach wychodzących
r - głębokość kolejki planisty krótkoterminowego

id - idle time określa % czas bezczynności procesora
Mogą być tego następujące przyczyny  :
  - blokada wątków wewnątrz aplikacji
  - aplikacja czeka na odpowiedz np z synchronicznego WS-* czy z bazy danych
  - aplikacja poprostu nie ma nic do roboty

- pidstat - monitorujemy procesy lub konkretny proces


  pidstat -w  - statystyka mimowolnych przełączeń kontekstu
 cswch/s - Total number of voluntary context switches the task made per second.  A voluntary context switch occurs when a task blocks because it requires a resource that is unavailable.

nvcswch/s -  Total number of non voluntary context switches the task made per second.  A involuntary context switch takes place when a task executes for the duration of its time slice and  then  is
                     forced to relinquish the processor.

Mimowolne przełączanie kontekstu zachodzi wtedy gdy wykonywany wątek jest usuwany z uwagi na przekroczenie limitu czasu lub zostanie wywłaszczony na rzecz innego wątku o wyższym priorytecie.

Wysoki poziom tego parametru wskazuje na saturację wątków, czyli jest więcej wątków w systemie niż procesów,  które mogłby te wątki obsłużyć.
Odpowiada to kolumnie r w narzędziu vmstat czyli kolejce planisty krótkoterminowego

pidstat -s - monitorujemy stos (a do czego jest stos o tym potem :))

pidstat -d 2 - monitorujemy operacje I/O w interwale co 2 sek
pidstat -r 2 - monitorujemy wykorzystanie pamięci

- mpstat

%usr - Show the percentage of CPU utilization that occurred while executing at the user level (application).

%nice - Show the percentage of CPU utilization that occurred while executing at the user level with nice priority.

%sys - Show the percentage of CPU utilization that occurred while executing at the system level (kernel). Note that this does not include time spent servicing hardware and software interrupts.

%iowait - Show the percentage of time that the CPU or CPUs were idle during which the system had an outstanding disk I/O request.

%irq - Show the percentage of time spent by the CPU or CPUs to service hardware interrupts.

%soft -Show the percentage of time spent by the CPU or CPUs to service software interrupts.

%steal - Show the percentage of time spent in involuntary wait by the virtual CPU or CPUs while the hypervisor was servicing another virtual processor.

%guest - Show the percentage of time spent by the CPU or CPUs to run a virtual processor.

%idle - Show the percentage of time that the CPU or CPUs were idle and the system did not have an outstanding disk I/O request.

  - sysstat - wymagana biblioteka dla iostat i vmstat

  - perf - monitorowanie procesów
   
  - nicstat  - ważne informację z poziomu monitorowania i przepustowości sieci (network bandwidth) lub wydajnością (network I/O performance).
   - iostat - wykorzystanie I/O
     iostat {interval } {how many time view result}
     Operacje I/O mają duży impakt na wydajność w szczególności jeśli mamy dużo zapisów lub odczytów małych ilości danych pochłania dużą ilość zasobów procesora (SYS/Kernel) , które można by było wykorzystać w inny sposób.

Powyższy wydruk pokazuje wykorzystanie procesora oraz operacje dyskowe na
każdym urządzeniu zamontowanym w systemie.
 źródło :  pomiar wydajności w linuxie
  • lk_wrtn/s: Indicate the amount of data written to the device expressed in a number of blocks per second.
  • Blk_read: The total number of blocks read.
  • Blk_wrtn: The total number of blocks written.
  • kB_read/s: Indicate the amount of data read from the device expressed in kilobytes per second.
  • kB_wrtn/s: Indicate the amount of data written to the device expressed in kilobytes per second.
  • kB_read: The total number of kilobytes read.
  • kB_wrtn: The total number of kilobytes written.
  • MB_read/s: Indicate the amount of data read from the device expressed in megabytes per second.
  • MB_wrtn/s: Indicate the amount of data written to the device expressed in megabytes per second.
  • MB_read: The total number of megabytes read.
  • MB_wrtn: The total number of megabytes written.
  • rrqm/s: The number of read requests merged per second that were queued to the device.
  • wrqm/s: The number of write requests merged per second that were queued to the device.
  • r/s: The number of read requests that were issued to the device per second.
  • w/s: The number of write requests that were issued to the device per second.
  • rsec/s: The number of sectors read from the device per second.
  • wsec/s: The number of sectors written to the device per second.
  • rkB/s: The number of kilobytes read from the device per second.
  • wkB/s: The number of kilobytes written to the device per second.
  • rMB/s: The number of megabytes read from the device per second.
  • wMB/s: The number of megabytes written to the device per second.
  • avgrq-sz: The average size (in sectors) of the requests that were issued to the device.
  • avgqu-sz: The average queue length of the requests that were issued to the device.
  • await: The average time (in milliseconds) for I/O requests issued to the device to be served. This includes the time spent by the requests in queue and the time spent servicing them.
  • svctm: The average service time (in milliseconds) for I/O requests that were issued to the device.
  • %util: Percentage of CPU time during which I/O requests were issued to the device (bandwidth utilization for the device). Device saturation occurs when this value is close to 100%.

Interesujące nas również parametry pamięci to : 
   - paging (stronicowanie)
   - swapping (wymiana stron)
     Następuje wtedy gdy jeśli aplikacja pobiera większa ilość pamięci niż dostępna pamięć fizyczna. Wtedy system korzysta pomocniczo z dysku to ma duży impakt na wydajność
   - locking (blokowanie) - analiza periodycznych zrzutów wątków (thread dumps) narzędzia to jvisualvm
   - context switching (przełączanie kontekstu) (mimowolne i dobrowolne)
     (narzędzie to : pidstat oraz vmstat)
   - thread migration  (migracja wątku) - migracja wątków wpływa na wydajność ponieważ po przełączeniu na inny rdzeń dane lub infomację o stanie gotowego do uruchomienia wątku mogą nie być już dostępne w pamięci podręcznej.


Monitoring Lock Contention - monitorowanie rywalizacji o blokady
Istnieje narzędzie pidstat w opcją -w podaje liczbę dobrowolnych przełączeń kontekstu dla wszystkich procesorów wirtualnych.

Jak obliczyć zmarnowane cykle zegara na przełączenia kontekstu ?
Bazując na informacjach zawartych w książce java performanceon multi-core platforms
temp = cswch/s  * 80 000 / liczba procesów wirtualnych
liczna_cykli = częstotliwość procesora
liczna marnowanych cykli to temp/liczba_cykli


Stare ale nadal aktualne informacje w postaci mind-maps :


Basic elements -> next



Brak komentarzy:

Prześlij komentarz