ChapterPDF Available

Programowanie równoległe w języku Java z wykorzystaniem biblioteki PCJ

Authors:

Abstract

The article describes research on new version of the PCJ library (Parallel Computing in Java) for parallel programming in the Java language. The PCJ library was created and developed in scope of doctoral dissertation. The library is the implementation of solutions developed thought research into new methods of parallel programming in the Java language using the PGAS (Partitioned Global Address Space) paradigm. In the dissertation, the library was benchmarked and compared to the MPI (Message Passing Interface) – current standard used in the HPC (High-performance computing) for creating concurrent applications. Article describes history of development of the PCJ library with its current version 5. The library is based on PGAS programming model, which allows for easy development of parallel applications for HPC or processing Big Data. The library can be used in the multicore computers, computing clusters, so the systems that consist of many nodes consisting of many cores. PCJ hides the communication inside node and between nodes. The library can be also used on a single workstation with Java Virtual Machine (JVM). Current version of the PCJ library uses mechanisms introduced into newest version of the Java language, such as lambda expressions or streams, and introduces new possibilities for transferring data and creating distributed objects.
Innowacje w polskiej nauce
w obszarze matematyki i informatyki
Przegląd aktualnej tematyki badawczej
Wydawnictwo Nauka i Biznes
2016
Innowacje w polskiej nauce
w obszarze matematyki i informatyki.
Przegląd aktualnej tematyki badawczej
Pod redakc naukową
Łukasz Koźmiński
Jacek Doskocz
Piotr Kardasz
Wydawnictwo Nauka i Biznes
2016
Redakcja naukowa:
dr inż. Łukasz Kuźmiński
Uniwersytet Ekonomiczny we Wrocławiu
Ul. Komandorska 118/120, 53-345 Wrocław
Katedra Rachunkowości, Controllingu, Informatyki i
Metod Ilościowych
Dr inż. Jacek Doskocz
Fundacja Rozwoju Nauki i Biznesu w obszarze Nauk
Medycznych i Ścisłych
Legnicka 65, 54-206 Wrocław
Klaster Badań Rozwoju i Innowacji, Klub
Innowacyjni Naukowcy
Dr inż. Piotr Kardasz
Wyższa Szkoła Informatyki Stosowanej we
Wrocławiu, Wydział Automatyki i
Robotyki, Wejcherowska 28, 54-239 Wrocław
Fundacja Rozwoju Nauki i Biznesu w obszarze Nauk
Medycznych i Ścisłych
Legnicka 65, 54-206 Wrocław
Klaster Badań Rozwoju i Innowacji
Recenzenci naukowi i branżowi
mgr inż. Benedykt Bryłka
dr inż. Jacek Doskocz
dr inż. Marek Doskocz
dr inż. Tomasz Janiczek
dr inż. Piotr Kardasz
dr inż. Jacek Kujawski
dr inż. Monika Michalska
mgr inż. Anna Okniańska
mgr Katarzyna Puchała
dr inż. Anna Stanclik (Kozik)
mrg inż. Olga Stępień
mgr Sławomir Stępień
dr Anna Mempel-Śnieżyk
dr inż. Łukasz Szałata
inż. Magdalena Tomaszewska
inż. Piotr Wroński
Prof. zw. dr hab. inż. Jerzy Zwoździak
Projekt okładki:
Inż. Piotr Wroński
Elementy grafiki na okładce pochodzą ze strony freepik.com.
Korekta:
mgr inż. Anna Kaczmarek-Gałęza
inż. Magdalena Gazdowicz
inż. Piotr Wroński
Wydanie pod patronatem merytorycznym:
Fundacji Rozwoju Nauki i Biznesu w obszarze Nauk Medycznych i Ścisłych
Klastra Badań i Rozwoju oraz Innowacji
Klubu Innowacyjni Naukowcy
ISBN: 978-83-947095-2-5
Niniejsza publikacja zawiera zbiór tematycznych prac prezentowanych przez uczestników, podczas konferencji w roku 2016 we
Wrocławiu: Ogólnopolska Konferencja Innowacyjni Naukowcy pod patronatem Ministerstwa Nauki i Szkolnictwa Wyższego,
Ogólnopolska Konferencja Innowacyjnych Projektów Badawczych oraz Start Innowacji. Zawarte w niniejszej publikacji artykuły zostały
zamieszczone na odpowiedzialność ich autorów, którzy przesłali ich treści Organizatorom konferencji oraz zatwierdzili poprawk i
recenzentów i korektorów.
email: wydawnictwo@wnib.pl
www.wnib.pl
WYDAWNICTWO NAUKA I BIZNES Sp. z o. o., z siedzibą w Brzeziny ul. Chęcińska 169, Poczta: Morawica, Kod pocztowy:26-026,
zarejestrowana w Krajowym Rejestrze Sądowym pod numerem KRS: 0000654271, kapitał zakładowy 50 00,00zł w całości wniesiony,
NIP: 6572927439, REGON: 366131514, strona internetowa: www.wnib.pl , Firma powstała jako start up Dolnośląskiego Akceleratora
Technologii i Innowacji.
Spis Treści:
Przedmowa .............................................................................................................................................. 4
Rozdział I: Symulacje komputerowe parametrów mikroskopowych i makroskopowych materiałów
przy pomocy Metody Elementów Dyskretnych
- Piotr Klejment, Alicja Kosmala, Natalia Foltyn, Wojciech Dębski ......................................................... 5
Rozdział II: Optymalizacja matematyczna procesu epoksydacji alkoholu allilowego 30-proc.
nadtlenkiem wodoru na katalizatorze Ti-SBA-15 oraz w acetonitrylu jako rozpuszczalniku - zmiany
selektywności przemiany alkoholu allilowego do glicydolu
- Edyta Makuch, Agnieszka Wróblewska ............................................................................................... 20
Rozdział III: Model matematyczny w strategii kształtowania odpowiedniej ceny i promocji na bazie
rabatów i upustów
- Jacek Wawrzostek, Szymon Ignaciuk .................................................................................................. 30
Rozdział IV: Propozycja procesu pozyskiwania danych rynkowych na potrzeby realizacji badań
poświęconych zastosowaniu informatyki w gospodarce
- Artur Machura ..................................................................................................................................... 42
Rozdział V: Platforma ActGo-Gate jako innowacyjny model integracji usług
- Wiesława Gryncewicz, Maja Leszczyńska ........................................................................................... 56
Rozdział VI: Interfejsy użytkownika stosowane w urządzeniach dźwigowych – stan aktualny i kierunki
rozwoju
- Karol Miądlicki ..................................................................................................................................... 67
Rozdział VII: Interfejsy wizyjne i rozszerzona rzeczywistość w sterowaniu żurawiami przeładunkowymi
- Karol Miądlicki, Mirosław Pajor........................................................................................................... 83
Rozdział VIII: Koncepcja zastosowania symulatora opartego o technologię wirtualnej rzeczywistości
do szkolenia maszynistów w zakresie efektywnego energetycznie prowadzenia pociągu
- Witold Bartnik, Małgorzata Ćwil ......................................................................................................... 96
Rozdział IX: Standard LTE jako perspektywa rozwoju technologii GSM-R
- Grzegorz Olczyk ................................................................................................................................. 105
Rozdział X: Metody komunikowania stanów afektywnych w urządzeniach mobilnych
- Jakub Trojanowski, Michał Folwarczny, Piotr Hrebieniuk, Remigiusz Szczepanowski ...................... 118
Rozdział XI: Programowanie równoległe w języku Java z wykorzystaniem biblioteki PCJ
- Marek Nowicki, Piotr Bała ..................................................................................................................130
130
Rozdział XI:
Programowanie równoległe w języku Java z wykorzystaniem
biblioteki PCJ
Marek Nowicki (1), Piotr Bała (2)
(1) Wydział Matematyki i Informatyki, Uniwersytet Mikołaja Kopernika w Toruniu,
ul. Chopina 12/18, 87-100 Toruń
Email: faramir@mat.umk.pl
(2) Interdyscyplinarne Centrum Modelowania Matematycznego i Komputerowego,
Uniwersytet Warszawski, ul. Prosta 69, 00-838 Warszawa
Email: bala@icm.edu.pl
STRESZCZENIE
Artykuł opisuje badania nad nową wersją biblioteki PCJ (Parallel Computing in Java)
służącej do programowania równoległego w języku Java.
Biblioteka PCJ została stworzona i rozwijana w ramach pracy doktorskiej [1]. Powstała
jako implementacja rozwiązań opracowanych w wyniku badań nad nowymi metodami
programowania równoległego w języku Java z wykorzystaniem paradygmatu PGAS
(Partitioned Global Address Space). W pracy doktorskiej biblioteka została przetestowana,
a uzyskane wyniki zostały porównane z MPI (Message Passing Interface) aktualnym
standardem używanym do tworzenia aplikacji współbieżnych w środowisku HPC
(High-performance computing).
Artykuł przedstawia historię rozwoju biblioteki PCJ wraz z jej aktualnie najnowszą
wersją 5. Biblioteka bazuje na modelu obliczeń rozproszonych PGAS, który pozwala na
łatwe rozwijanie aplikacji równoległych niezbędnych do przetwarzania dużych danych (Big
Data) czy obliczeń wielkoskalowych (HPC).
Biblioteka pozwala na pracę na komputerach wielordzeniowych, klastrach
obliczeniowych, czyli systemach posiadających wiele węzłów, z których każdy składa się
z wielu rdzeni. PCJ ukrywa przed programistą szczegóły komunikacji wewnątrz
pojedynczego węzła i pomiędzy węzłami. Biblioteka może być również wykorzystywana na
pojedynczej stacji roboczej wyposażonej w Wirtualną Maszynę Javy (JVM).
Aktualna wersja biblioteki PCJ korzysta z mechanizmów dostępnych w najnowszej
edycji języka Java jak wyrażenia lambda czy strumienie, wprowadza nowe możliwości
przesyłania danych i pozwala na łatwiejsze tworzenie rozproszonych obiektów.
ABSTRACT
The article describes research on new version of the PCJ library (Parallel Computing in
Java) for parallel programming in the Java language.
The PCJ library was created and developed in scope of doctoral dissertation [1]. The
library is the implementation of solutions developed thought research into new methods of
parallel programming in the Java language using the PGAS (Partitioned Global Address
Space) paradigm. In the dissertation, the library was benchmarked and compared to the MPI
131
(Message Passing Interface) current standard used in the HPC (High-performance
computing) for creating concurrent applications.
Article describes history of development of the PCJ library with its current version 5.
The library is based on PGAS programming model, which allows for easy development of
parallel applications for HPC or processing Big Data.
The library can be used in the multicore computers, computing clusters, so the systems
that consist of many nodes consisting of many cores. PCJ hides the communication inside
node and between nodes. The library can be also used on a single workstation with Java
Virtual Machine (JVM).
Current version of the PCJ library uses mechanisms introduced into newest version of
the Java language, such as lambda expressions or streams, and introduces new possibilities
for transferring data and creating distributed objects.
Słowa kluczowe: Programowanie równoległe, Java, Podzielona globalna przestrzeń
adresowa, PGAS, HPC, PCJ
Keywords: Parallel programming, Java, Partitioned Global Address Space, PGAS, HPC,
PCJ
WSTĘP
W 1965 roku Gordon Moore sformułował prawo empiryczne, które mówi, że
ekonomicznie optymalna liczba tranzystorów w układzie scalonym zwiększa się w kolejnych
latach zgodnie z trendem wykładniczym [2][3]. Prawo Moore’a zostało rozszerzone na inne
parametry komputerów (np. szybkość przetwarzania).
Od 2005 roku częstotliwość zegara procesora nie zwiększa się. Jest to spowodowane
problemami z wydzielaniem temperatury i zakłóceniami z tym związanymi. Zamiast tego
zdecydowano się na zwiększenie liczby jednostek obliczeniowych, czyli procesorów i liczby
rdzeni procesora. W związku z tym coraz większą rolę zaczęło odgrywać programowanie
równoległe.
Aktualnie standardem programowania równoległego w HPC (High-performance
computing) jest MPI (Message Passing Interface). Jednak programowanie z wykorzystaniem
tego standardu jest trudne i mało wygodne, gdyż stosowane jest głównie z językami niskiego
poziomu jak C czy Fortran.
Językiem wysokiego poziomu, który jest w czołówce najczęściej wykorzystywanych
języków programowania jest Java. Java od początku swojego istnienia była zorientowana na
programowanie równoległe – warto wspomnieć chociażby klasę java.lang.Thread czy
słowo kluczowe synchronized, które istniały w języku Java od początku.
Jednak programowanie równoległe w języku Java z wykorzystaniem standardowych
mechanizmów pozwala wykorzystywać jedynie jeden węzeł obliczeniowy. Brakuje dobrych
rozwiązań do przeprowadzania obliczeń równoległych dla języka Java w środowisku
wielowęzłowym. Istnieją i całkiem dobrze znane biblioteki-frameworki jak Hadoop czy
Spark, ale za ich pomocą rozwiązuje się głównie tylko jeden typ problemów: przeszukiwanie
dużych zbiorów danych. Niektórych problemów nie da się lub jest bardzo trudno sformułować
w modelu MapReduce, który wspomniane biblioteki wykorzystują.
W niniejszym artykule zostanie przedstawiona historia i aktualnie najnowsza wersja
biblioteki służącej do obliczeń równoległych w języku Java jaką jest PCJ w wersji 5.
132
HISTORIA ROZWOJU BIBLIOTEKI PCJ
Biblioteka PCJ została stworzona i rozwijana w ramach studiów doktoranckich i pracy
doktorskiej autora referatu [1]. Powstała ona jako implementacja rozwiązań opracowanych
w wyniku badań nad nowymi metodami programowania równoległego w języku Java
z wykorzystaniem paradygmatu PGAS (Partitioned Global Address Space).
Wersja pierwsza
Pierwsze prace nad biblioteką PCJ rozpoczęły się na przełomie września
i października 2011 roku. Wśród założeń do biblioteki było dostarczenie mechanizmów, które
pozwalałaby na uruchomienie zadań równoległych na klastrze obliczeniowym, w taki sposób,
by na każdym węźle klastra działała jedna instancja PCJ zawierająca wątek wykonujący
obliczenia. Ta pojedyncza instancja miała zawierać miejsce zwane magazynem (ang.
storage) zawierającym zmienne współdzielone (ang. shared), które mogłyby dać się
przesyłać. Przekazywanie wartości zmiennych współdzielonych miało być możliwe przez
jednokierunkowe, asynchroniczne metody: get i put, których jednym z parametrów miał być
identyfikator wątku przeciwnego. Biblioteka zawierać miała też metodę do synchronizacji
wszystkich wątków – sync.
Pierwsza, prototypowa implementacja biblioteki PCJ została ukończona w listopadzie
2011 roku. Została napisana z wykorzystaniem najnowszej w danym momencie wersji języka
i platformy Java, czyli Java SE 7.
W pierwszej wersji biblioteki PCJ zaimplementowano opisane powyżej mechanizmy,
gdzie punktem startowym obliczeń została wybrana klasa implementująca interfejs
StartPoint. Magazynem była klasa, która rozszerzała klasę StorageAbstract, która
z kolei implementowała interfejs Storage. Zmienne współdzielone w tej klasie były
oznaczane za pomocą adnotacji @Shared zawierającej jako parametr nazwę, która miała
być wykorzystywana jako parametr dla metod get i put. Nazwy klas punktu startowego
i magazynu były przekazywane do odpowiedniej metody statycznej klasy PCJ startującej
obliczenia. Obliczenia były uruchamiane na węzłach klastra według opisu zawartego w pliku
XML. Plik ten zawierał m.in. informację o menadżerze instancji JVM, która była
odpowiedzialna za koordynację węzłów obliczeniowych (wątków wykonujących obliczenia)
jego adresie IP i porcie, na którym nasłuchiwał oraz informacje o węzłach obliczeniowych –
również ich adresach IP oraz portach.
W dalszej pracy nad biblioteką adnotacja @Shared zyskała mechanizm jej
procesowania tak, by w trakcie kompilacji było możliwe sprawdzenie, czy wskazuje ona na
zmienną, którą da się przesłać siecią za pomocą serializacji, czyli czy klasę można rzutować
na interfejs java.io.Serializable. Dodatkowo zostało dodane sprawdzenie, czy pole
z adnotacją @Shared znajduje się w klasie implementującej interfejs Storage.
Następnie został dodany mechanizm rozgłaszania wartości zmiennej. Skorzystano ze
specjalnego typu komunikatu związanego z rozgłaszaniem wiadomości, gdzie wiadomość
rozgłaszana była jej załącznikiem. Każdy węzeł najpierw przekazywał wiadomość dalej,
a następnie rozpakowywał załącznik i wykonywał operacje wg załącznika, czyli w przypadku
rozgłaszania wartości zmiennej załącznikiem był komunikat put.
Ze względu na to, że rozgłaszanie jak i operacja put były asynchroniczne,
nieblokujące i jednostronne, a operacja synchronizacji była barierą sprawdzającą, czy
wszystkie węzły dotarły do tego samego miejsca obliczeń, nie było możliwe upewnienie się,
133
że dany węzeł obliczeniowy otrzymał nową wartość zmiennej współdzielonej. Stąd potrzebny
był mechanizm pozwalający sprawdzać, czy został otrzymany komunikat zmieniający
wartość zmiennej. W tym celu powstały metody monitor i waitFor, które odpowiednio
czyszczą licznik zmian zmiennej i zatrzymują wykonanie aktualnego wątku do momentu
wystąpienia zmiany wartości. Dodatkowo stworzono metodę pozwalającą na synchronizację
nie tylko całej grupy węzłów, ale również podzbioru węzłów obliczeniowych. Dzięki tym
zmianom możliwe było upewnienie się, że dany węzeł otrzymał i zdążył pobrać nową
wartość zmiennej współdzielonej przed kolejną zmianą jej wartości.
Kolejnym krokiem rozwoju biblioteki było dodanie możliwości tworzenia nowych grup
i dołączania do nich. W tym momencie synchronizacja podzbioru węzłów obliczeniowych nie
była konieczna i w dalszych wersjach biblioteki ta możliwość została usunięta, choć
pozostawiono możliwość synchronizacji dwóch węzłów obliczeniowych.
Na koniec prac nad PCJ1, metoda get otrzymała bliźniaczą metodę get z dodanym
nowym parametrem FutureResponse. Nowa metoda działała w sposób nieblokujący. Po
wysłaniu żądania, utworzony obiekt FutureReponse był zapamiętywany w wewnętrznej
mapie, tak by po otrzymaniu odpowiedzi móc go wypełnić otrzymaną wartością. Dzięki temu
obiekt ten pozwalał na sprawdzenie, czy oczekiwana wartość została już zwrócona, a jeśli
nie to czekanie na tę wartość oraz zwrócenie oczekiwanej wartości.
Wersja druga
W sierpniu 2012 roku rozpoczął się proces tworzenia drugiej wersji biblioteki PCJ.
Wersja ta była krokiem milowym w rozwoju biblioteki. W tej wersji zmieniono podstawową
koncepcję tak, by na jednym węźle nie uruchamiać jednego, a wiele wątków obliczeniowych
wątków PCJ.
Wcześniej na jednym węźle można było uruchomić wiele instancji PCJ, każdą
nasłuchującą na innym porcie, i w ten sposób można było w pełni wykorzystać dostępne
rdzenie obliczeniowe węzła, na którym obliczenia były uruchamiane. Wiązało się to
z oczywistym narzutem na pamięć (wiele instancji wirtualnej maszyny Java) czy na
komunikację (dwie instancje JVM znajdujące się na tym samym węźle przekazywały sobie
dane przez przesyłanie ich poprzez gniazda TCP).
W drugiej wersji biblioteki możliwe było przekazywanie danych przez przekazywanie
samych referencji. Wiązało się to z sytuacją, w której programy zapisane w PCJ1 mogłyby
nie działać poprawnie w PCJ2 z tego powodu, że zmienne statyczne w PCJ1 były ściśle
związane z danym węzłem obliczeniowym, podczas gdy w PCJ2 zmienne statyczne mogły
być współdzielone. Postanowiono odseparować wątki obliczeniowe w ramach jednej JVM.
Poradzono sobie z tym przez wykorzystanie osobnych ładowarek klas (ang. ClassLoader)
dla różnych wątków PCJ. Dokładniejszy opis tego mechanizmu został zawarty w rozprawie
doktorskiej autora artykułu [1].
Dalszym etapem prac było dodanie specjalnych wątków – deserializerów, których
zadaniem było zdeserializowanie otrzymanych danych, z wykorzystaniem odpowiednich
ładowarek klas, do postaci akceptowalnej przez odpowiednie wątki PCJ. Dzięki temu wątki
odpowiedzialne za odbieranie danych i przetwarzanie wiadomości zlecały najbardziej
czasochłonne operacje do innych wątków i mogły równolegle przetwarzać kolejne
wiadomości.
W wersji drugiej PCJ zmieniono również zduplikowany kod związany z pobieraniem
danych metodę get w obu jej wariantach (blokującą i nieblokującą z obiektem
FutureResponse). Zastąpiono te metody nieblokującą metodą getFutureObject, która
134
zwolniła użytkownika biblioteki z samodzielnego tworzenia odpowiedniego obiektu Future,
gdyż metoda sama zwracała ten obiekt. Metoda get pozostała blokująca, ale w swoim
działaniu wykorzystywała metodę getFutureObject i obiekt przez tę metodę zwracany:
PCJ.getFutureObject(id, varName).get();
Kolejną ważną zmianą w PCJ2 było dodanie do metod get, getFutureObject i put
opcjonalnego argumentu będącego odwołaniem do indeksów tablicowej zmiennej
współdzielonej.
Dodatkowo dodano operacje getLocal i putLocal działające dla aktualnego wątku
obliczeń, które do magazynu wkładały referencje do istniejących obiektów w przeciwieństwie
do metod get i put, które tworzyły ich kopię.
Wersja trzecia
W listopadzie 2013 roku rozpoczęto prace nad trzecią wersją biblioteki PCJ. Nie była
ona już tak bardzo przełomowa jeśli chodzi o nowe funkcjonalności. Wśród głównych zmian
było uporządkowanie nazw metod, by lepiej odzwierciedlały wykonywane operacje (np. sync
na barrier, numNodes na threadCount, czy myNode na myId). Wykonano też pewne
operacje konserwacyjne biblioteki.
Trzecia wersja biblioteki była opisywana w doktoracie [1] a za jej pomocą stworzono
aplikacje będące częścią HPC Challenge Benchmark Suite, których implementacje
zaprezentowano na konferencji Supercomputing 2014, gdzie biblioteka PCJ uzyskała
nagrodę Most elegant solution w kategorii HPC Challenge Class 2: Most Productivity.
Wersja czwarta
Rozwój czwartej wersji biblioteki PCJ rozpoczął się w sierpniu 2014 roku zmianą nazw
pakietów wykorzystywanych klas z pl.umk.mat.pcj.** na org.pcj.**.
W wersji czwartej zrezygnowano z osobnych ładowarek klas dla różnych wątków PCJ
ze względu na problemy z wykorzystaniem bibliotek używających natywnych metod. Dana
biblioteka mogła być załadowana dokładnie raz. Kolejne ładowanie powodowało rzucenie
wyjątku podobnie jak próba korzystania z natywnych metod w wątku posiadającym inną
kopię załadowanej klasy zawierającej metody natywne, tzn. klasę załadowaną za pomo
innej ładowarki klas. Jedyną możliwością byłoby używanie klas z metodami natywnymi
załadowanymi za pomocą systemowej ładowarki klas, co wiązałoby się z dużymi
utrudnieniami dla użytkowników biblioteki.
Dodatkowym argumentem za zmianą był brak możliwości całkowitego rozdzielenia
przestrzeni klas dla różnych wątków PCJ, ponieważ klasy z pakietów java.**, javax.**, sun.**
musiały być ładowane przez systemową ładowarkę klas, czyli były wspólne dla wszystkich
wątków PCJ na jednej wirtualnej maszynie Java.
W PCJ4 dodano również metodę cas (ang. compare and swap, pol. porównaj
i zamień), ale ze względu na słabą wydajność w dalszych pracach usunięto ją.
Wersja piąta
Prace nad piątą wersją biblioteki PCJ rozpoczęły się w czerwcu 2016 roku, choć już
wcześniej pewne jej elementy były rozważane. 25 czerwca 2016 roku powstała wersja alpha
piątej wersji biblioteki PCJ. Wśród głównych, chociaż niewidocznych, zmian był zmieniony
mechanizm komunikacji między węzłami dzięki czemu możliwe stało się uruchamianie zadań
na 1024 węzłach klastra (prawie 50 000 rdzeni obliczeniowych). Ponadto nowy mechanizm
135
pozwolił na przesyłanie komunikatów o rozmiarze większym niż 2GB, które w poprzednich
wersjach biblioteki powodowałyby problemy.
Tworząc piątą wersję biblioteki postanowiono zupełnie wyczyścić ją z elementów
związanych z własnymi ładowarkami klas, które w czwartej wersji zostały jedynie ukryte.
Piąta wersja przyniosła również sporo rewolucyjnych zmian w programistycznym
interfejsie aplikacji (API).
PCJ W WERSJI PIĄTEJ
W pracach nad piątą wersją biblioteki PCJ przyświecała myśl, by zmodyfikować
zgodnie z doświadczeniami nabytymi w trakcie tworzenia aplikacji korzystających z PCJ.
Postanowiono także wykorzystać najnowszą dostępną wersję języka i platformy Java, czyli
Java SE 8, gdyż wersja ta wprowadziła przełomowe zmiany, takie jak możliwość pisania
wyrażeń lambda, przetwarzanie strumieniowe czy domyślne metody w interfejsach.
Nazwy podstawowych metod
Pierwszym elementem, na który została zwrócona uwaga, to nazwy podstawowych
metod używanych w PCJ: get, put, broadcast, barrier. Wszystkie te metody mają ten
sam schemat nazewnictwa, ale różnie się zachowują:
get jest synchroniczne czeka na odpowiedź,
put działa w sposób asynchroniczny po wysłaniu aplikacja działa dalej, bez
pewności czy dane zostały już przetworzone przez odbiorcę,
broadcast podobnie jak put jest asynchroniczne,
barrier działa synchroniczne zatrzymuje działanie i czeka na możliwość
kontynuowania pracy.
Widać, że dwie metody asynchroniczne i dwie synchroniczne. Rozważano dwie
możliwości zmiany nazewnictwa metod, by było ono bardziej spójne:
1. Zmiana nazwy z get na syncGet i pozostawienie put oraz broadcast bez
zmian. Do tego dodanie metod syncPut i syncBroadcast oraz get
działającego w sposób asynchroniczny.
2. Pozostawienie metody get bez zmian, ale zmiana put na asyncPut oraz
broadcast na asyncBroadcast, Do tego dodanie put i broadcast, które
działałyby w sposób synchroniczny, to znaczy, by czekały na potwierdzenie
wykonania operacji dostarczenia zmiennej, oraz użycie asyncGet zamiast
getFutureObject z PCJ4.
Z doświadczenia wydaje się, że im krótsza nazwa metody, tym częściej będzie
używana, więc opcja pierwsza, w której get działa asynchronicznie byłaby bardziej
wskazana.
Jednakże została jeszcze jedna istotna metoda: barrier. W pierwszej opcji barrier
wydaje się, że powinien działać asynchronicznie, czyli zwracać obiekt typu Future<Void>,
który można przetestować, by wiedzieć, czy inne wątki dotarły do tego miejsca
synchronizacji, a jak nie to wykonywać inne prace. To rozwiązanie jest niestety niezbyt
intuicyjne. Dla barrier w wersji asynchronicznej lepszą nazwą byłoby: asyncBarrier, ale
ten prefiks skłania do wyboru opcji drugiej, co oznaczałoby, że metoda Xxx działa
synchronicznie, a jedynie metody o nazwach asyncXxx działają asynchronicznie. Można by
zastanowić się jeszcze, czy potrzebna jest metoda asyncBarrier, ale z doświadcz
136
własnych jak i doświadczeń innych języków opartych o paradygmat PGAS [4], taka operacja
może być użyteczna.
W efekcie wybrano drugą opcję nazewnictwa metod.
Nazwy zmiennych
Z doświadczeń z pisaniem aplikacji dla PCJ4 jak i wcześniejszych wynika, że
korzystanie z nazw zmiennych będących ciągami tekstowymi (ang. String) jest uciążliwe:
łatwo o literówkę w nazwie, aplikację korzystająca z tego typu zmiennych trudniej
refaktoryzować i utrzymywać, brakuje wsparcia zintegrowanych środowisk
programistycznych (IDE). Ponadto nie ma możliwości korzystania z wielu magazynów,
tworzenia ponownie używalnych bibliotek dla PCJ ze względu na możliwą kolizję nazw.
Z tego powodu postanowiono wykorzystać stałe typów wyliczeniowych dla nazewnictwa
nazw zmiennych. Dzięki korzystaniu z typu wyliczeniowego refaktoryzacja jest prostsza,
trudniej o pomyłkę w nazwie zmiennej a także o kolizję nazw. Zrezygnowano też z adnotacji
@Shared.
Po tej zmianie zmienna współdzielona jest rozpoznawalna nie tylko po swojej nazwie,
ale także po nazwie magazynu, z którego pochodzi.
Początkowo nazwą magazynu była pełna nazwa typu wyliczeniowego, ale to wiązało
się z przechowywaniem danych o typie zmiennej równi w tym typie wyliczeniowym, co
powodowało konieczność pisania kodu analogicznego do poniższego:
public enum WspoldzielonyTypWyliczeniowy implements Shared {
/* nazwa zmiennej współdzielonej z jej typem
jako stała typu wyliczeniowego */
liczbaCalkowita(int.class),
tablicaLiczbDlugich(long[].class),
opakowanaLiczbaZmiennoprzecinkowa(Double.class);
private final Class<?> typElementu;
private WspoldzielonyTypWyliczeniowy(Class<?> typElementu) {
this.typElementu = typElementu;
}
@Override
public Class<?> type() {
return typElementu;
}
}
Skorzystanie z interfejsu Shared pozwoliło na tworzenie zmiennych współdzielonych
w locie przez utworzenie klasy implementującej interfejs Shared i zwracającej odpowiednie
wartości dla metody name, type oraz parent (domyślna metoda z interfejsu Shared
zwracająca nazwę klasy).
Taką klasę należało następnie w programie zarejestrować, by można było z niej
skorzystać. Dzięki temu możliwe było skorzystanie z więcej niż jednego magazynu.
Niestety, w tym rozwiązaniu nie było możliwe, by zmienne będące polami klasy były
jednocześnie zmiennymi współdzielonymi.
Po kolejnych kilku iteracjach związanych z nazwami zmiennych ustalono rozwiązanie,
które wydaje się być optymalne. Dodano w nim dwie adnotacje:
137
adnotację @Storage(Class<?>), którą dodaje się do typu wyliczeniowego, gdzie
parametrem jest klasa zawierająca zmienne współdzielone zgodne z nazwami
stałych typu wyliczeniowego,
adnotację @RegisterStorages(Class<? extends Enum<?>>[]), którą można
dołożyć do klasy implementującej interfejs StartPoint, by podane w parametrze
odwołania do typów wyliczeniowych zawierających nazwy zmiennych
współdzielonych przy starcie danej aplikacji były automatycznie rejestrowane.
Poniżej znajduje się przykładowy program korzystający z obu adnotacji:
@RegisterStorages(WitajSwiecie.DaneWspoldzielone.class)
public class WitajSwiecie implements StartPoint {
@Storage(WitajSwiecie.class)
enum DaneWspoldzielone {
tablica
}
private static final int[] tablica = new int[PCJ.threadCount()];
private long suma;
@Override
public void main() throws Throwable {
}
}
Jak widać od wersji PCJ5 zmienne współdzielone mogą być statyczne (wspólne dla
wszystkich wątków działających na danej JVM) oraz finalne (brak możliwości zmiany ich
wartości po zainicjowaniu). W powyższym programie występuje tylko jedna zmienna
współdzielona o nazwie tablica, natomiast pole suma jest lokalne dla każdego wątku PCJ.
Kolejność parametrów metod
W trakcie tworzenia PCJ5 usystematyzowano również kolejność parametrów dla metod
get, put oraz broadcast. W tym przypadku również rozważono różne kombinacje
kolejności parametrów wywołania tych metod. Zmian wymagała kolejność tylko metod put
i broadcast w taki sposób, by nazwa zmiennej była jak najbliżej opcjonalnego argumentu
zawierającego indeksy, tak jak ma to miejsce w metodzie get:
get(threadId, variableName, optionalIndices...)
Tworząc metodę put na podstawie metody get, jedyną pozycją, na której podaje się
nową wartość zmiennej współdzielonej jest pierwsza pozycja:
put(newValue, threadId, variableName, optionalIndices...)
Podobnie sprawa wygląda w metodzie broadcast, gdzie pozycja parametru
zawierająca nową wartość zmiennej współdzielonej została ustalona na pozycję pierwszą:
broadcast(value, variable)
Najważniejsze zmiany w PCJ5
Poniżej podsumowano najważniejsze zmiany w PCJ5, które wprowadziła ta wersja,
a których nie było dostępnych w poprzednich wersjach.
W najnowszej wersji biblioteki dodano obsługę wyjątków mogących pojawić się
w czasie przesyłania zmiennych współdzielonych. Teraz programista ma możliwość złapania
wyjątku i odpowiedniego zareagowania na niego. W poprzednich wersjach takie sytuacje nie
były obsługiwane.
138
Została dodana notyfikacja zakończenia operacji put i asynchroniczna bariera, a dla
wszelkich operacji asynchronicznych została dodana możliwość czasowego czekania na ich
zakończenie.
Stworzono możliwość obsługi wielu magazynów, dla których zmienne współdzielone
zostały zmienione z typu ciągu tekstowego na stałe typów wyliczeniowych. Dodatkowo
możliwe jest skojarzenie magazynów z klasą startową implementującą interfejs StartPoint
i dla niej automatycznie następuje ich rejestracja przy starcie aplikacji.
Ponadto tworzenie magazynów odbywa się w momencie, gdy znany jest już
identyfikator wątku i liczba wątków, co pozwala na łatwiejszą inicjalizację zmiennych
współdzielonych.
Portowanie aplikacji z PCJ4 do PCJ5
Większość pracy wymaganej przy portowaniu aplikacji do PCJ5 polega na
automatycznej lub półautomatycznej modyfikacji kodu źródłowego.
Pierwszą modyfikacją jest zamiana metody log, która już nie występuje w bibliotece
PCJ, na wywołanie metody System.out.println.
Klasy zawierające zmienne współdzielone nie rozszerzają już klasy Storage,
a zmienne współdzielone nie zawierają adnotacji @Shared, za to ich nazwy muszą znaleźć
się w typie wyliczeniowym, zaadnotowanym przez @Storage, którego parametrem jest
dawna klasa rozszerzająca klasę Storage.
Następnie w klasach używających zmiennych współdzielonych należy wykonać
zamianę parametrów, by zamiast korzystać typu String korzystać z typu wyliczeniowego.
Tę operację dość prosto można zapisać korzystając z wyrażeń regularnych.
Dodatkowo w klasie zawierającej wywołanie metody start lub deploy należy usunąć
drugi argument i dodać typ wyliczeniowy, który wskazywał na podaną klasę jako parametr
adnotacji @RegisterStorages. Trzeci argument, który stał się drugim, należy opakować
w: new NodesDescription(<argument>).
Następną zmianą jest zamiana nazwy klasy z FutureObject na
PcjFuture, z getFutureObject na asyncGet, i podobnie z getObject na get.
Kolejne zmiany to zmiana nazwy put na asyncPut i broadcast na asyncBroadcast.
Oczywiście warto prześledzić, co dzieje się w zmienianym kodzie, by jak najlepiej
wykorzystać nowe możliwości PCJ5.
PRZYKŁADOWY KOD APLIKACJI W PCJ5
W tym rozdziale zostanie przedstawiony pełen kod źródłowy prostej aplikacji
z wykorzystaniem PCJ5.
Zmiany pomiędzy PCJ4 a PCJ5
Poniżej zaprezentowano przykładowy program obliczający przybliżoną wartość liczby
π za pomocą metody całkowania (prostokątów) zapisaną z wykorzystaniem PCJ4 oraz
PCJ5. Linie z PCJ4 zostały oznaczone przez znak - (minus), natomiast linie z PCJ5 przez +
(plus). Linie wspólne dla obu wersji PCJ pozostały bez prefiksu.
-import org.pcj.FutureObject;
import org.pcj.PCJ;
+import org.pcj.PcjFuture;
139
+import org.pcj.RegisterStorages;
-import org.pcj.Shared;
import org.pcj.StartPoint;
import org.pcj.Storage;
+@RegisterStorages(DaneWspoldzielone.class)
-public class ObliczPiCalkowaniem extends Storage implements StartPoint {
+public class ObliczPiCalkowaniem implements StartPoint {
+ @Storage(ObliczPiCalkowaniem.class)
+ public enum DaneWspoldzielone {
+ suma
+ }
- @Shared
private double suma;
private double funkcjaPodCalka(double x) {
return (4.0 / (1.0 + x * x));
}
@Override
public void main() throws Throwable {
double pi = obliczWartoscPi();
if (PCJ.myId() == 0) {
System.out.format("Pi wynosi %.15f\n", pi);
}
}
private double obliczWartoscPi() {
int liczbaPunktow = 1_000_000;
double ulamek = 1.0 / (double) liczbaPunktow;
for (int i = PCJ.myId() + 1; i <= liczbaPunktow;
i += PCJ.threadCount()) {
suma = suma + funkcjaPodCalka(((double) i - 0.5) * ulamek);
}
suma = suma * ulamek;
PCJ.barrier();
if (PCJ.myId() == 0) {
- FutureObject[] dane = new FutureObject[PCJ.threadCount()];
+ PcjFuture<Double>[] dane = new PcjFuture[PCJ.threadCount()];
for (int i = 1; i < PCJ.threadCount(); ++i) {
- dane[i] = PCJ.getFutureObject(i, "suma");
+ dane[i] = PCJ.asyncGet(i, Shared.suma);
}
for (int i = 1; i < PCJ.threadCount(); ++i) {
- suma = suma + (double) dane[i].getObject();
+ suma = suma + dane[i].get();
}
}
return suma;
}
public static void main(String[] args) {
PCJ.deploy(ObliczPiCalkowaniem.class,
- ObliczPiCalkowaniem.class, "nodes.txt");
+ new NodesDescription("nodes.txt"));
}
}
140
PODSUMOWANIE
Biblioteka PCJ jest elastyczna i stosunkowo prosta w użyciu, dzięki czemu może być
bardzo szybko zaadaptowana do rozwiązania istniejących problemów, tak by wykorzystać
moc aktualnych komputerów. Wydajność aplikacji napisanych z wykorzystaniem języka Java
i PCJ jest zbliżona do wydajności aplikacji tworzonych z wykorzystaniem tradycyjnych
języków programowania. Biblioteka jest stale rozwijana i jest z sukcesem wykorzystywana do
zrównoleglania aplikacji naukowych. Wśród nich można wymienić aplikację wykorzystującą
optymalizację wieloparametryczną (algorytm genetyczny) do modelowania kontektomu
nicienia (Caenorhabditis elegans) [5], implementację przetwarzania danych grafowych
w ramach zbioru testów Graph500 [6], czy zrównoleglenie programu BLAST do szybkiego
znajdowania podobnych sekwencji DNA.
Wbrew krążącym mitom, rozwiązania w języku Java o jaki oparta jest biblioteka, mogą
być tak samo szybkie, a nawet szybsze od rozwiązań bazujących na językach niższego
poziomu jak C++. Biblioteka PCJ, dostępna jako oprogramowanie otwarte (licencja BSD) [7]
to idealne narzędzie do rozwoju aplikacji wymagających dużych obliczeń i przetwarzania
danych.
PODZIĘKOWANIA
Prace były częściowo finansowane w ramach projektu HPDCJ finansowanego przez
konsorcjum CHIST-ERA (w Polsce finansowane przez NCN: 2014/14/Z/ST6/0007).
Obliczenia zostały wykonane przy wsparciu Interdyscyplinarnego Centrum
Modelowania Matematycznego i Komputerowego (ICM) Uniwersytetu Warszawskiego
w ramach grantu obliczeniowego nr GB65-15.
LITERATURA
[1] M. Nowicki (2015). Opracowanie nowych metod programowania równoległego w Javie
w oparciu o paradygmat PGAS (Partitioned Global Address Space). Rozprawa
doktorska, Uniwersytet Warszawski, 2015 http://ssdnm.mimuw.edu.pl/pliki/prace-
studentow/st/pliki/marek-nowicki-d.pdf [Dostęp: 26.08.2016]
[2] G. Moore (1965). Cramming more components onto integrated circuits, Electronics,
Volume 38, Number 8, April 19, 1965.
[3] Prawo Moore’a Wikipedia, wolna encyklopedia, https://pl.wikipedia.org/wiki/
Prawo_Moore’a [Dostęp: 26.08.2016]
[4] J. Mellor-Crummey, L. Adhianto, W. Scherer III i in. (2009). A new vision for Coarray
Fortran. In Proceedings of the Third Conference on Partitioned Global Address Space
Programing Models (p. 5). ACM.
[5] Ł. Górski, F. Rakowski, P. Bała (2015). Parallel Differential Evolution in the PGAS
Programming Model Implemented with PCJ Java Library. In International Conference
on Parallel Processing and Applied Mathematics (pp. 448-458). Springer International
Publishing.
[6] M. Ryczkowska, M. Nowicki, P. Bała (2016). The Performance Evaluation of the Java
Implementation of Graph500. In Parallel Processing and Applied Mathematics (pp.
221-230). Springer International Publishing.
[7] Biblioteka PCJ: http://pcj.icm.edu.pl [Dostęp: 26.08.2016]
ResearchGate has not been able to resolve any citations for this publication.
Article
Full-text available
In 1998, Numrich and Reid proposed Coarray Fortran as a simple set of extensions to Fortran 95 [8]. Their principal extension to Fortran was support for shared data known as coarrays. In 2005, the Fortran Standards Committee began exploring the addition of coarrays to Fortran 2008, which is now being finalized. Careful review of drafts of the emerging Fortran 2008 standard led us to identify several shortcom-ings with the proposed coarray extensions. In this paper, we briefly critique the coarray extensions proposed for Fortran 2008, outline a new vision for coarrays in Fortran language that is far more expressive, and briefly describe our strategy for implementing the language extensions that we propose.
Chapter
Graph-based computations are used in many applications. Increasing size of analyzed data and its complexity make graph analysis a challenging task. In this paper we present performance evaluation of Java implementation of Graph500 benchmark. It has been developed with the help of the PCJ (Parallel Computations in Java) library for parallel and distributed computations in Java. PCJ is based on a PGAS (Partitioned Global Address Space) programming paradigm, where all communication details such as threads or network programming are hidden. In this paper, we present Java implementation details of first and second kernel from Graph500 benchmark. The results are compared with the existing MPI implementations of Graph500 benchmark, showing good scalability of PCJ library.
Conference Paper
New ways to exploit parallelism of large scientific codes are still researched on. In this paper we present parallelization of the differential evolution algorithm. The simulations are implemented in Java programming language using PGAS programing paradigm enabled by the PCJ library. The developed solution has been used to test differential evolution on a number of mathematical function as well as to fine-tune the parameters of nematode's C. Elegans connectome model. The results have shown that a good scalability and performance was achieved with relatively simple and easy to develop code.
Article
The future of integrated electronics is the future of electronics itself. Integrated circuits will lead to such wonders as home computers, automatic controls for automobiles, and personal portable communications equipment. But the biggest potential lies in the production of large systems. In telephone communications, integrated circuits in digital filters will separate channels on multiplex equipment. Integrated circuits will also switch telephone circuits and perform data processing. In addition, the improved reliability made possible by integrated circuits will allow the construction of larger processing units. Machines similar to those in existence today will be built at lower costs and with faster turnaround.
Opracowanie nowych metod programowania równoległego w Javie w oparciu o paradygmat PGAS (Partitioned Global Address Space). Rozprawa doktorska
  • M Nowicki
M. Nowicki (2015). Opracowanie nowych metod programowania równoległego w Javie w oparciu o paradygmat PGAS (Partitioned Global Address Space). Rozprawa doktorska, Uniwersytet Warszawski, 2015 http://ssdnm.mimuw.edu.pl/pliki/pracestudentow/st/pliki/marek-nowicki-d.pdf [Dostęp: 26.08.2016]