Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

TGT#11 - Zostały Ci jeszcze jakieś włosy? (Testowanie programów równoległych)​​​ - Artur Opaliński

172 views

Published on

Zostały Ci jeszcze jakieś włosy? (Testowanie programów równoległych)​​

Published in: Software
  • Be the first to comment

  • Be the first to like this

TGT#11 - Zostały Ci jeszcze jakieś włosy? (Testowanie programów równoległych)​​​ - Artur Opaliński

  1. 1. Zostały Ci jeszcze jakieś włosy? Testowanie programów równoległych Artur.Opalinski@pg.gda.pl
  2. 2. Czego dowiesz się z tej prezentacji? • Na czym polega programowanie równoległe i kiedy się je stosuje? • Co to jest wyścig (race) ? • Jakie są rodzaje błędów związanych z programowaniem równoległym ? • Jakie są techniki znajdowania błędów w programach równoległych? • Jakie narzędzia służą do testowania programów równoległych? • Jak wykorzystać powyższe narzędzia do szybszego testowania programów nie działających równolegle?
  3. 3. Programy równoległe Programy równoległe Typowo, program komputerowy opisuje działania do kolejnego wykonania * współbieżność (concurrency) * równoległość (parallelism) W tej prezentacji używany jest tylko termin równoległość, ale problematyka w równym stopniu dotyczy współbieżności W programach równoległych działania opisane programem wykonywane są równocześnie(*)
  4. 4. Programy równoległe Programy równoległe Processors are not going to get much faster. No higher clockspeeds are foreseen. The speed of processing will be further increasing through parallellization, engaging multiple CPU cores for handling all tasks rather than a single faster core. [ LUCAS JELLEMA, ORACLE] Big Data Artificial Intelligence IBM BlueGene Massive Parallel Computer Graphics Computer Vision
  5. 5. Przykład: Sumowanie liczb Programy równoległe Typowo, program komputerowy opisuje działania do kolejnego wykonania * współbieżność (concurrency) * równoległość (parallelism) W tej prezentacji używany jest tylko termin równoległość, ale problematyka w równym stopniu dotyczy współbieżności W programach równoległych działania opisane programem wykonywane są równocześnie(*)
  6. 6. Przykład: Sumowanie kolejnych liczb od 1..99 Programy równoległe Wyznaczanie sumy s: s ← s + 1 s ← s + 2 s ← s + 3 ..... s ← s + 99 Ostatecznie s zawiera sumę liczb od 1 do 99 s ← 0 (jeszcze nic nie jest zsumowane) Strzałka: ← oznacza przypisanie nowej wartości do s (niech stanie się równe)
  7. 7. Przykład: Zrównoleglone sumowanie liczb od 1..99 Programy równoległe Wyznaczanie sumy s: s ← s + 1 s ← s + 2 s ← s + 3 ..... s ← s + 50 s ← s + 51 s ← s + 52 ..... s ← s + 97 s ← s + 98 s ← s + 99 W s spodziewana jest suma liczb od 1 do 99 s ← 0 (jeszcze nic nie jest zsumowane) Grupa A dodawań Grupa B dodawań Grupa C dodawań Grupy działań A, B, C nie są od siebie zależne, zatem można wykonywać je równolegle tworząc sumy częściowe, i zsumować te sumy częściowe po zakończeniu działań A,B,C. (możliwe jest wiele innych podziałów tych działań na grupy, np.. można oddzielnie dodawać liczby parzyste, i oddzielnie nieparzyste, itp.)
  8. 8. Programy równoległe Kod w Java: Sumowanie kolejnych liczb (od liczby start, do liczby end) class Serial { static volatile protected long s = 0; protected long start; protected long end; Serial(long start, long end){ this.start = start; this.end = end; } void sum() { for(long i = this.start; i < this.end; i++) s = s + i; } long getS() { return s; } } Metoda sum() służy do sumowania liczb od start do end Suma początkowo 0 Ustalenie zakresu sumowania Suma liczb od 1..99 daje 4950 Zmienna s niepotrzebnie zadeklarowana jest jako static. Nie jest to jawnym błędem w tym miejscu, ale spowoduje błąd w innych częściach kodu (c.d.n.)
  9. 9. Programy równoległe 1/2: Kod w Java: Zrównoleglone sumowanie liczb (od liczby start, do liczby end) class Parallel extends Serial implements Callable<Long> { Parallel(long start, long end){ super(start, end); } public Long call(){ sum(); return s; } } Metoda call() może działać równolegle - w oddzielnych wątkach, sumujących odrębne zakresy liczb Dziedziczenie i implementowanie Klasa Parallel ● Prowadzi obliczenia korzystając z metody sum() pochodzącej z klasy Serial (=z poprzedniego slajdu). ● implementuje interfejs Callable z Java, który pozwala uruchamiać wiele równoległych wątków działania; każdy wątek korzystając z sum() może sumować odrębne zakresy liczb.
  10. 10. Programy równoległe 2/2: Kod w Java: Zrównoleglone sumowanie liczb (od liczby start, do liczby end) static long parallel(int nthrds){ ExecutorService executor = Executors.newCachedThreadPool(); List<Future<Long>> list = new ArrayList<Future<Long>>(); long[] ranges = computeRanges(nthrds); for (int i = 0; i < nthrds; i++) { Callable<Long> worker = new Parallel(ranges[i],ranges[i+1]); Future<Long> submit = executor.submit(worker); list.add(submit); } long sum = 0; // now retrieve the result for (Future<Long> future : list) { try { System.out.println("Got "+future.get()+" from future:" + future.toString()); sum += future.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } executor.shutdown(); return sum; } Tworzone są oddzielne wątki z klasy Parallel. Powstaje nthrds wątków; każdy sumuje liczby z innego podzakresu liczb ranges[]. Wyniki działania oddzielnych wątków (=sumy częsciowe) są sumowane w zmiennej sum.
  11. 11. Programy równoległe 4 wątki, liczby 1..1011 -1, 27.612s: 8 wątków, liczby 1..1011 -1, 25.740s: System nieobciążony: Obciążenie rdzeni, 8 cores CPU
  12. 12. Programy równoległe 8 wątków, liczby 1..1012 -1 Stan wątków Java VisualVM (bundled since Java SE in JDK v.6, u.7.)
  13. 13. Wyścig (race) Kod do sumowania kolejnych liczb, po uruchomieniu równoległym przestał produkować poprawny wynik Kod sumujący Zakres liczb Czas Suma kolejne liczby: 1 .. 9 <1ms 45 kolejne liczby: 1 .. 99 <1ms 4950 kolejne liczby: 1 .. 109 -1 1.045s 499999999500000000 kolejne liczby: 1 .. 1010 -1 9.812s -5340232226128654848 kolejne liczby: 1 .. 1011 -1 93.210s 932355974711512064 „równolegle” w 1 wątku: 1 .. 109 -1 1.065s 499999999500000000 „równolegle” w 1 wątku: 1 .. 1010 -1 9.980s -5340232226128654848 „równolegle” w 1 wątku: 1 .. 1011 -1 93.640s 932355974711512064 równolegle w 4 wątkach: 1 .. 109 -1 343ms 579476633044106679 równolegle w 4 wątkach: 1 .. 1010 -1 2.698s -1808996355356915478 równolegle w 8 wątkach: 1 .. 1010 -1 2.501s -8627543040327612036 równolegle w 8 wątkach: 1 .. 1011 -1 25.942s -8429917773521836662 Ponieważ sumowanie wielu dużych liczb powoduje przepełnienia typu long, wyniki nie stanowią sumy matematycznej. Nie traktujemy tego jako błędu; to może być niekiedy pożądana funkcjonalność (np. w tym przykładzie ogranicza długość porównywanych wyników). Czas wykonania zmienia się zgodnie z oczekiwaniami (nie badano statystycznie). Błędnewyniki
  14. 14. Wyścig (race) 43 getfield Serial.start : long [20] ; weź wartość this.start... 46 lstore_1 [i] ; ...i wpisz do zmiennej i 47 goto 62 ; sprawdź warunek zakończenia pętli 50 getstatic Serial.s : long [12] ; weź wartość zmiennej s 53 lload_1 [i] ; weź wartość zmiennej i 54 ladd ; dodaj wartości s oraz i (ze stosu) 55 putstatic Serial.s : long [12] ; zapisz nową wartość zmiennej s 58 lload_1 [i] ; weź wartość zmiennej i 59 lconst_1 ; weź stałą wartość 1 60 ladd ; dodaj wartości (ze stosu) 61 lstore_1 [i] ; zapisz wynik jako nową wartość i 62 lload_1 [i] ; warunek pętli: weź wartość zmiennej i 63 aload_0 [this] ; odłóż na stos referencję this 64 getfield Serial.end : long [22] ; weź wartość zmiennej i 67 lcmp ; porównaj wartość this.end i zmiennej i 68 iflt 50 ; ew. kontunuuj pętlę Bytecodes pętli: for(long i = this.start; i < this.end; i++) s = s + i; i = this.start s = s + i i++ i < this.end
  15. 15. Wyścig (race) 1/2: Instrukcja s=s+i to nie jest jedna operacja JVM s Wątek 1 Wątek 2 Wątek 3 Wątek n Wątek n-1 Wszystkie wątki próbują modyfikować jedną, tę samą zmienną s w podobnym czasie, tworząc swoje sumy częściowe. Działanie wątków nie jest ciągłe. Po każdej(*) operacji może być potencjalnie, chwilowo przerwane, w celu realizacji innego wątku (być może wątku innej aplikacji) Momenty przerywania działania wątków zależą od tak wielu czynników, że można przyjąć iż są praktycznie nie do przewidzenia. (*) „Po każdej może” nie oznacza, że „po wszystkich występuje”
  16. 16. Wyścig (race) 2/2: Instrukcja s=s+i to nie jest jedna operacja JVM Możliwa (i częsta w przykładzie) jest np. sekwencja operacji, w której jeden wątek nadpisze wyniki innego wątku: Wątek k: Weź wartość s Weź wartość i Dodaj wartości s oraz i (przerwanie działania na pewien czas) Zapisz nową wartość s Wątek m: Weź wartość s Weź wartość i Dodaj wartości s oraz i Zapisz nową wartość s W tym momencie wartość zapisana przez wątek m zostanie nadpisana przez wątek k (=wartość z wątku m utracona) czas
  17. 17. Wyścig (race) Wyniki bywają niepoprawne tylko w przypadku, gdy klasę Parallel uruchomiono w więcej niż jednym wątku. Kod wykonywany równolegle przestaje być deterministyczny. Efekt działania kodu równoległego może zależeć od kolejności wykonania wątków. W zależności od tego, który wątek wykona pewna operację szybciej (tu: zmodyfikuje zmienną s) wynik programu jest różny. To zjawisko to wyścig. Kod do sumowania kolejnych liczb, po uruchomieniu równoległym przestał produkować poprawny wynik
  18. 18. Rodzaje błędów • W programach równoległych możliwe są wszystkie te błędy, które występują w programach nie zrównoleglonych. • W programach nie zrównoleglonych błędy są deterministyczne (reprodukowalne): program wykazuje ten sam błąd zawsze – pod warunkiem jednakowego stanu poczatkowego i takich samych danych wejściowych. • W programach równoległych możliwe są: ● race conditions ● data races ● deadlocks ● missed signals ● live locks ● starvation • Błędy związane z programowaniem równoległym
  19. 19. Techniki testowania • Testy automatyczne trudne, bo też wymagają programowania równoległego • Zachowanie testowanego programu mniej przewidywalne i niepowtarzalne ● trudno o reprodukowalność błędu: błędy mogą manifestować się bardzo rzadko, tylko w pewnych konfiguracjach platformy (klient vs test lab !) • Próby monitorowania czy debuggowania programu modyfikują timing i momenty synchronizacji które mogą całkowicie zapobiegać wystąpieniu błędu w tak kontrolowanym środowisku (zasada Heisenberga) • Zaplanować więcej czasu na testy części zrównoleglonych kodu Wyzwania testowania programów równoległych
  20. 20. Techniki testowania • Wydzielenie zrównoleglonych zadań do zamkniętych, niewielkich części programu. • Stosowanie obiektów thread-safe, (effetive) immutable (żaden wątek nie modyfikuje obiektu, który znalazł się we współdzielonej (shared) strukturze) • Testy zjawisk losowych dają wyniki losowe. Trzeba wykonać takich testów więcej ● ale w zmiennych warunkach • Testować wpływ zmiany stanu, w tym zależności czasowe: ● np. przy wstawianiu i usuwaniu z kolejki: uwzględnić wszystkie możliwe w aplikacji relacje czasowe i kolejności tych operacji • Badać przeplatanie (interleaving) kodu Testowanie równoległego kodu w zasadzie(*) jest niemożliwe: to próba udowodnienie braku czegoś w systemie niedeterministycznym • (*) Poza rzadkimi przypadkami w których daje się opisać wszystkie możliwe kombinacje wpływających czynników, i kombinacji tych nie jest zbyt dużo Techniki tworzenia i testowania kodu zrównoleglonego ATesting shows the presence, not the absence of bugs, [E.Dijkstra]
  21. 21. Techniki testowania • W celu zapobieżenia błędom testów: ● Podczas testów, jawnie zarządzać cyklem życia wątków testujących i testowanych ● Uruchamiać testy z timeout ● Uruchamiać testowane zadania w wątku testowym (=ze wstrzykniętą makietą Executora) ● Jeśli testowany wątek czeka z rozpoczęciem działania, wydzielić to działanie do odrębnej metody i wywoływać w testach bezpośrednio ● Generując zdarzenia dla programu testującego, testowany wątek może powiadamiać o zmianach swojego stanu Techniki tworzenia i testowania kodu zrównoleglonego
  22. 22. Inne rozwiązania • Minimalizacja i wydzielenie kodu do zadań zrównoleglonych (powtórzenie z poprzednich slajdów) • Tworzenie kodu z myślą o testach (powtórzenie z poprzednich slajdów) • Statyczna analiza kodu ● np. FindBugs znajduje niespójności w synchronizacji zmiennych Zapobieganie błędom w programach równoległych
  23. 23. Narzędzia testowania • Bazujące na JUnit (głównie do równoległego wykonywania testów) ● JUnitPerf (dodatkowe dekoracje do JUnit) ● ConcJUnit (fork JUnit wspierający pisanie testów programów równoległych; ostatnie wydanie 2010) ● JUnit Tolbox (klasy do równoległego uruchamiania testowanych wątków, 2015) ● ConcurrentRunner w Junit (deprecated?) • JCStress (Linux only, bundled with OpenJDK) ● Dla wskazanych aktorów (wątków) tworzy automatycznie kombinacje działania: równoległego i nie równoległego, pod obciążeniem i bez obciążenia, wielokrotnego • Thread-weaver (Java, ostatnia aktualizacja kodu 2014) ● Framework do pisania wielowątkowych Unit Testów w Java. ● W testowanym kodzie umieszcza punkty kontrolne (breakpoints). Pozostałe wątki działają, podczas gdy zmodyfikowane zatrzymują się w tych punktach. Powtarzalne testy wyścigu i innych aspektów poprawności wątków. • Intel Thread Checker (OpenMP) ● Dynamiczna analiza kodu źródłowego, uzupełnianego o elementy monitoringu i logowania, w celu wykrywania deadlocks, zawieszeń, wyścigów, itp Narzędzia do testowania kodu równoległego
  24. 24. Narzędzia testowania @RunWith(Parameterized.class) public class ParallelTest { @Parameters public static java.util.Collection<Object[]> generateParams() { java.util.List<Object[]> params = new java.util.ArrayList<Object[]>(); for (int i = 0; i < 10; i++) params.add(new Object[] {i}); return params; } private int thrds; public ParallelTest(Object p) { thrds = (Integer)p; } @Test public void testParallel() { assertEquals(499999999500000000L, TGT.parallel(thrds)); } } Testowanie kodu równoległego JUnit z klasą ParallelComputer Przygotowanie parametrów testu. Nawet, jeśli nie są realnie używane w testach, wymuszają liczbę powtórzeń W celu przeszukania możliwie dużej przestrzeni rozwiązań, uruchamia się wiele testów. Dla klasy testującej przygotowywany jest zestaw parametrów dla testów. Każda metoda testująca z tej klasy zostanie wywołana dla każdego parametru z zestawu. Każdy test produkuje swoją, oddzielną ocenę. Wykorzystanie parametrów do testowania z różną liczbą wątków Metoda testująca zostanie wywołana dla każdego parametru testu z przygotowanego wyżej zestawu
  25. 25. Narzędzia testowania • Niektóre narzędzia pozwalają tworzyć równoległe testy (=nie tylko do testowania zrównoleglonego kodu) • Zaletą może być większa przepustowość testów • JUnit (v.4.7+) z ParallelComputer pozwala uruchomić równolegle ● wszystkie metody testujące z wszystkich klas testujących ● kolejne metody testujące z wszystkich klas testujących ● Wszystkie metody testujące ze wskazanej klasy • TestNG pozwala ustalić więcej parametrów zrównoleglenia niż Junit: ● Swobodnie definiować grupy testów ● Ustalić liczbę wątków testujących ● Ustalic liczbę powtórzeń testu (@Test(invocationCount = 10) ) • Tempus-fugit • parallel-junit • Test Load Balancer • Drobne klasy rozproszonych autorów (google for: junit concurrent testing) : ParallelSuite , ParallelParameterized Równoległe testowanie kodu
  26. 26. Narzędzia testowania public class TestDemo { @Test public void test() { Class<?>[] classes= {SerialTest.class, ParallelTest.class, ParallelTest.class}; System.out.print("Running tests:n"); //simultaneously all methods in all classes: Result result = JUnitCore.runClasses(new ParallelComputer(true, true), classes); assertTrue(result.wasSuccessful()); System.out.print("nFinished running tests"); } } Równoległe testowanie kodu JUnit z klasą ParallelComputer public class ParallelTest { @Test public void testParallel() { assertEquals(499999999500000000L, TGT.parallel(8)); } } public class SerialTest { @Test public void testSerial() { assertEquals(499999999500000000L, TGT.serial()); } } Wywołuje obie klasy do równoległego działania (klasę ParallelTest w dwóch egzemplarzach, zgodnie z powyższą inicjalizacją zmiennej classes. Wynik wszystkich testów jest jeden.
  27. 27. Pełniejsze informacje • JUnit: ● Junit Tutorial: http://www.tutorialspoint.com/junit/ JUnit Wiki: https://github.com/junit-team/junit4/wiki ● Tomek Kaczanowski, Practical Java Unit testing using JUnit and Mockito, http://practicalunittesting.com/index.php O tworzeniu dobrych (readable and maintainable), testów. Kluczowe koncepcje unit testów (mocks, stubs, parametrized tests, matches) Plus rozdział o Test Driven Development (TDD). • Programowanie równoległe i współbieżne: ● Brian Goetz, Java Concurrency in Practice Książki dotyczące omawianych zagadnień
  28. 28. Pełniejsze informacje • Specjalność kształcenia Informatyka w Systemach Sterowania na kierunku Automatyka i Robotyka • II stopień (magisterski) • 3 semestry studiów ● + Ew. wyrównanie różnic programowych • Nowoczesne laboratoria Wydział Elektrotechniki i Automatyki PG
  29. 29. In Java VisualVM parlance, as mapped in ThreadMXBeanDataManager.j ava: Ready/Running → RUNNING Timed Waiting → SLEEPING a thread waiting in Thread.sleep() or Object.wait (with timeout specified), Thread.join (with timeout specified), or method yield() was called on the thread object or PARK a thread waiting in sun.misc.Unsafe.park(), presumably via LockSupport.parkNanos() or LockSupport.parkUntil()). A thread can only park itself, there is no way to park other threads. When a thread calls park() to park itself, this disables the current thread for thread scheduling purposes unless the permit is available. Permit is be given to a dormant thread by calling unpark() from another thread. Waiting → WAIT a thread in the waiting state and this wait is over only when some other thread performs some appropriate action. A thread can get into this state either by calling - Object.wait (without timeout), Thread.join (without timeout), or LockSupport.park methods Blocked → MONITOR a thread which has been blocked and is waiting for a moniotor to enter/re-enter a synchronized block/method. A thread gets into this state after calling Object.wait method and then getting a notify() or notifyAll(), or when thread’s wait-time ends Terminated, New → ZOMBIE

×