poniedziałek, 5 listopada 2012

Listy w Scali

Ostatnimi czasy biorę intensywnie udział w kursie programowania funkcyjnego w Scali na portalu Coursera.org. Generalnie kiedyś się zraziłem do tego języka, gdyż jakiś tutorial i aktualnie używana przeze mnie wersja środowiska, powodowała, że przekopiowane przykłady nie działały. Teraz jest już trochę lepiej, aczkolwiek IDE do Scali dalej kuleją.

Na sam kurs zapisałem się zachęcony przez kolegę z pracy – fana Scali. Pomyślałem, że skoro nie będę robił tego sam, a będzie wsparcie w razie czego, to warto się zaangażować. I rzeczywiście jeszcze kilka znajomych robi ten kurs. Ostatnio nawet organizuję cykliczne spotkania w pracy, podczas których prezentujemy i porównujemy nasz kod. Oczywiście omawiamy stare zadania, które są już po deadline, aby nie łamać Honor Code. Jeśli macie taką możliwość, to zachęcam do organizowania podobnych aktywności u siebie.

Czy uczestnictwo w takim kursie coś daje? Nawet jeśli Scala nie trafi do powszechnego użytku, to na pewno uczestnictwo w kursie pomaga w zrozumieniu paradygmatu funkcyjnego i wynikającego z niego konsekwencji. Horyzonty się poszerzają, co ostatnio pomogło mi w lepszym zrozumieniu Java Scripta na darmowym kursie we Wrocławiu. Więcej o tym szkoleniu w innym wpisie.

Dla mnie wielką satysfakcją jest moment, kiedy dostaję olśnienia i nagle całkiem odmiennie patrzę na coś, co używam na co dzień. Podczas tego kursu był już taki moment, podczas rozwiązywania zadań z drugiego tygodnia. Należało wówczas zaimplementować zbiór (Set). Zadanie niby proste, ale nie gdy w kodzie mamy coś takiego:
type Set = Int => Boolean 
Długo nie mogłem zrozumieć, o co chodzi. Czytałem wątki na forum kursu, szukałem w Necie... W końcu zrozumiałem, że w tym przypadku Set został zdefiniowany, jako funkcja przyjmująca wartość liczbową, a zwracająca informację, czy dana liczba należy do zbioru, czy nie. Całkiem odmienne spojrzenie, na coś co zawsze było pudełkiem przechowującym elementy. Jak widać, można inaczej. Co prawda sprawia to parę problemów (możliwy zakres elementów), ale wyjście po za tą ramę było dla mnie niesamowite.

Dobra, koniec tych przemyśleń, czas na konkrety. Listy towarzyszą uczestnikom kursu już od samego początku. Zostały one jednak dokładniej przedstawione, dopiero podczas piątego tygodnia.

Listy w Scali są niezmienne. Co prawda istnieje alternatywna implementacja: MutableList, ale chodzi o to aby używać niezmienników. Wiadomo dlaczego. Nie oznacza to jednak, że jak wrzucimy element do listy (a ściślej mówiąc stworzymy listę zawierającą dany element), to nie możemy zmienić stanu tegoż obiektu. Owszem możemy. W Javie było by to tak:
class Person {
    private final String name;
    public int variable = 0;

    public Person(String name, int variable) {
        this.name = name;
        this.variable = variable;
    }

    public void change() {
        variable = variable + 42;
    }
}

public class Main {

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Person("Jan", 0));
        List immutableList = Collections.unmodifiableList(list);
        System.out.println(immutableList.get(0).variable);
        immutableList.get(0).change();
        System.out.println(immutableList.get(0).variable);
    }
} 

W Scali można analogicznie:
class Person(name: String, var variable: Int) {
  def change() {
    variable = variable + 42
  }
}

object Main {

  def main(args: Array[String]) {

    val person = new Person("name", 0)
    val list = List(person)
    println(list(0).variable)
    list(0).change()
    println(list(0).variable)
  }
} 

Tworzenie list w Skali mocno różni się od list w Javie. Z racji ich nie zmienności, musimy ich zwartość zdefiniować w momencie tworzenia.
val person = new Person("name", 0)
val list = List(person) 

Warto przy tej okazji zobaczyć, do czego będzie w tym momencie ewaluowane wywołanie List(person). Otóż List w Scali to:
  • abstakcyjna klasa List
  • objekt List
Klasa i objekt jednocześnie? Prawie. W Scali słówko kluczowe object oznacza definicję singletona. Po prostu ten wzorzec został wbudowany w język. Z praktycznego punktu widzenia, oznacza to, że możemy mieć tylko jedną instancję tegoż obiektu. Nie trzeba więc używać słówka new w tym przypadku.

Z drugiej strony, możemy przecież wywołać naszego singletona wielokrotnie z różnymi argumentami.
val list = List(person)
val list2 = List(person)
Jakby podejrzeć adresy w pamięci, to się okaże, że nie wskazują one tego samego miejsca. Co się więc dzieje? Jeśli chcemy skorzystać z dobrodziejstwa niepisania new, należy w naszym object’cie zdefiniować metodę apply() zwracającą żądaną instancję. Oczywiście metoda ta może przyjmować argumenty i zwraca żądaną instancję. Pełni wiec to bardziej rolę Fabryki niż singletonu. Jak to jest zdefiniowane dla listy? a no tak:
override def apply[A](xs: A*): List[A] = xs.toList 
Czyli na naszych argumentach (typu WrappedArray) zostanie wywołana metoda toList() i zadnie będzie gdzieś tam daleko delegowane...

No dobra, a jakiego typu listę otrzymamy? Patrząc na List.scala to można zobaczyć, że są dwie klasy rozszerzające tą Listę:
  • case object Nil extends List[Nothing]
  • final case class ::[B](...) extends List[B]

Pierwsza konkretna implementacja to element końcowy listy, za którym nic już nie ma. Natomiast dwukropek dwukropek, to nazwa klasy! Trochę się zszokowałem, gdy to zobaczyłem – rozumiem nazwać tak metodę, ale klasę? Jak widać można. Klasa ta tak naprawdę skleja głowę z ogonem. Implementacja jest bardzo prosta:
final case class ::[B](private var hd: B, private[scala] var tl: List[B]) extends List[B] {
  override def head : B = hd
  override def tail : List[B] = tl
  override def isEmpty: Boolean = false 

A samego operatora używamy zazwyczaj w bardziej czytelnej formie
val person = new Person("name", 0)
val person2 = new Person("name2", 0)
val list = List(person2)
val list2 = person :: list 

A jak za pomocą :: utworzyć jednoelementową listę? Należy skorzystać ze wspomnianego już Nil’a:
val list3 = person :: Nil 
Analogicznie jak :: działa +: Ten drugi operator (a właściwie metoda) dodatkowo tworzy kopię bazowej listy. Możemy również dodawać elementy na końcu listy, ale raczej nie jest to optymalne. W tym celu używamy :+. Przykład:
scala> val text = List("Ala", "ma")
text: List[java.lang.String] = List(Ala, ma)

scala> val text2 = text :+ "kota"
text2: List[java.lang.String] = List(Ala, ma, kota) 

Można również sklejać listy. Do tego używamy potrójnego dwukropka ::: Przykład:
scala> val numbers = List(4, 6, -3, 1)
numbers: List[Int] = List(4, 6, -3, 1)

scala> val numbers2 = List(2, 3)
numbers2: List[Int] = List(2, 3)

scala> val numbers3 = numbers2 ::: numbers
numbers3: List[Int] = List(2, 3, 4, 6, -3, 1) 

Operator ten dokleja nowe elementy na początku istniejącej listy. Podobnie działa dwuplus ++, który dodatkowo tworzy kopię Listy:
scala> val numbers4 = numbers2 ++ numbers
numbers4: List[Int] = List(2, 3, 4, 6, -3, 1) 

Nie wiem jak by to można było na przykładzie przedstawić (a debugować mi się nie chce), po prostu musimy ufać dokumentacji.

Uff przebrnęliśmy szczęśliwie przez te ‘śmieszne’ operatory. Muszę przyznać, że dla początkującego adepta Scali, są te operatory trochę niejasne i mylące, dlatego warto poświęcić im chwilę. Istnieje jeszcze kilka metod produkujących listy:
xs.reverse - zwraca odwróconą listę
xs updated (n, x) - zwraca listę zawierającą to samo co xs, ale w miejscu o indeksie n wstawia x’a
xs.toList – metoda dostępna w wielu innych typach

Co jeszcze ciekawego mamy w listach w Scali?

Mamy 3 fundamentalne operacje:
head – zwraca pierwszy element
tail – zwraca wszystko poza pierwszym elementem
isEmpty – zwraca true dla :: i false dla Nil

Ponadto lista zawiera sporo innych już mniej wydajnych operacji (last, init, take…), jak i takie które znamy dobrze z Javy (contains, indexOf…).

Scala udostępnia nam trochę więcej operacji na listach, niż mamy to w czystej Javie. I tak na przykład przekształcenie listy w listę zawierającą inne elementy. W Javie zazwyczaj piszemy petlę:
List<String> names = new ArrayList<String>();
for (Person person : persons) {
    names.add(person.getName());
} 

W Scali można to wykonać prościej za pomocą map():
val names = persons map (p => p.getName) 

Czyli dla każdego elementu p z listy persons wywołaj getName i zwróć to wszystko opakowane w listę i zapisz w names. Czy jest to czytelniejsze? Jak się pamięta co robi metoda map() i jeśli jest się w stanie sobie wyobrazić co oznacza p, to tak. O ile jak czytam własny kod to jestem w stanie przeanalizować co się dzieje, ale jak bym miał to zrobić z czyimś kodem, to bym się pewnie musiał dłużej zastanawiać, o co kaman.

Tą, jakże krótką i zwięzłą definicję, można jeszcze zapisać na kilka sposobów:
val names = persons map (p => p getName)
val names = persons map (_.getName)
val names = persons map (_ getName) 

Następną przydatną operacją jest filtrowanie, czyli pobranie podzbioru listy. W Javie to by było jakoś tak:
List<Person> personsStartsWithJ = new ArrayList<Person>();
for (Person person : persons) {
    if(person.getName().startsWith("J")) {
        personsStartsWithJ.add(person);
    }
} 

W Scali można to zapisać na jeden ze sposobów:
val personsStartsWithJ = persons filter(p => p.getName.startsWith("J"))
val personsStartsWithJ = persons filter(_.getName.startsWith("J")) 

Dodatkowo mamy metodę filterNot(), która zwraca elementy niespełniające podanego warunku. Można również wykonać te dwie operacje (filter i filterNot) jednocześnie:
val personsPart = persons partition(_.getName.startsWith("J"))
println(personsPart._1)
println(personsPart._2) 

Przykładowy efekt działania:
List(Person(Jan))
List(Person(Stefan), Person(Emil), Person(Dominik)) 

Czyli bardzo sensowne użycie tuple’a, pomagające zwrócić dwa wyniki z metody. Nie kojarzę żadnych standarowych metod z Javy zwracających dwa wyniki. Tak wiem że SRP, ale jak akurat potrzebujemy obydwie listy, to dwukrotna iteracja może rzutować na performance. W Javie trzeba to na piechotę robić:
List<Person> personsStartsWithJ = new ArrayList<Person>();
List<Person> personsNotStartsWithJ = new ArrayList<Person>();
for (Person person : persons) {
    if(person.getName().startsWith("J")) {
        personsStartsWithJ.add(person);
    } else {
        personsNotStartsWithJ.add(person);
    }
} 

To na razie chyba tyle co chciałem przedstawić, zapisać, zapamiętać i podzielić się tym z Wami na temat Scali. Czas wrócić do oglądania kolejnych wykładów...

wtorek, 16 października 2012

Logowanie interakcji w Mockito

Ostatnio przyszedł do mnie mój architekt i zaczął rozmowę coś w stylu:
Marcin, ty nasz ekspercie od testowania jednostkowego, integracyjnego i Mockito…
Ucieszony miłymi słowami pochwały i podbudowany faktem, że stanowię ważną część zespołu słuchałem dalej, co ma mi do powiedzenia kolega z zespołu. Wtedy jeszcze nie wiedziałem, że to pierwsze zdanie to tak naprawdę prosta, skuteczna, magiczna sztuczka, dzięki której bardzo łatwo jest później przekonać odbiorcę tego komunikatu, do wykonania pewnego zdania. To już trochę zahacza o manipulację / wywoływanie (ang. elicitation) / urabianie / programowanie neurolingwistyczne, czy też inne tego typu techniki, a więc wykracza po za tematykę tego bloga.

Wracając do rozmowy:
No bo właśnie mamy taki problem, że zrobiliśmy refaktoring niedawno stworzonej klasy i generalnie wszystko działa, ale trzeba jeszcze testy dopasować. Ten i tamten już poszli do domu, więc pytam czy byś tego nie zrobił. Uważam, że Tobie, jako że jesteś najbardziej w testach zorientowany, pójdzie szybko i gładko...
No dobra, myślę sobie. Dzisiaj kończy się czas przeznaczony na kodowanie w tym Sprincie, trzeba więc cos zdeployować, aby testerzy mieli jutro co robić. No dobra, obowiązki wzywają.

Architekt pokazał mi i opisał jakie to zmiany zostały wykonane. Generalnie był to pewien proces, który kodowaliśmy przez cały sprint. Miał on wiele danych wejściowych i generował sporo na wyjściu. No i wcześniej była sobie taka jedna metoda, która to miała 6 argumentów, kilka mocków i produkowała coś pożytecznego. Zostało to opakowane w fabrykę, która podawała teraz cześć danych od dupy strony (wywołanie innych serwisów), jak i również sama metoda została przeorana. Czyli liczba argumentów zmalała, doszło kilka zależności, nowy sposób instancjowania, no i trzeba był do tego przerobić testy.

Cofnięcie zmian nie wchodziło w rachubę, gdyż była to sprawka w sumie 3ch osób, a wykorzystywany system kontroli wersji (dziękujemy IBM Synergy) zrobił by więcej złego niż dobrego. Olanie testów jedynie przesuwało problem w czasie, a na syndrom wybitego okna nie mogłem sobie pozwolić.

Zacząłem więc to po kolei przerabiać testy, ich czytelność malała, a definicje zachowania mocków stawały się niezrozumiałe. Najgorsze że pierwszy test, który przerabiałem, ciągle nie działał. Nie wiedziałem w końcu czy to wina domyślnych wartości zwracanych przez Mocki z Mockito, czy też trefnego refactoringu (a raczej redesignu).

No i w tym momencie postanowiłem sprawdzić interakcje z moimi mockami, czy czasem gdzieś przypadkiem Mockito nie zwraca np. pustej listy, która to powoduje brak wyników. Chwila szperania w dokumentacji i znalazłem taki ciekawy twór. Przykładowy kod poniżej.
@Test
public void shouldCreateAccount() {
    // given
    InvocationListener logger = new VerboseMockInvocationLogger();
    AccountService accountService = new AccountService();
    StrengthPasswordValidator validator = mock(
            StrengthPasswordValidator.class,
            withSettings().invocationListeners(logger));
    UserDao userDao = mock(UserDao.class,
            withSettings().invocationListeners(logger));
    UserFactory userFactory = mock(UserFactory.class,
            withSettings().invocationListeners(logger));

    when(userFactory.createUser(LOGIN, PASSWORD))
            .thenReturn(new User(LOGIN, PASSWORD));

    accountService.setPasswordValidator(validator);
    accountService.setUserDao(userDao);
    accountService.setUserFactory(userFactory);

    // when
    User user = accountService.createAccount(LOGIN, PASSWORD);

    // then
    assertEquals(LOGIN, user.getLogin());
    assertEquals(PASSWORD, user.getPassword());
} 
Testujemy zakładanie konta użytkownika. Życiowy przykład, wiele nie trzeba tłumaczyć.

Na początek w linii 4tej tworzymy instancję obiektu typu VerboseMockInvocationLogger. Jest to standardowa implementacja interfejsu InvocationListener, która wypisuje na standardowe wyjście zaistniałe interakcje z mockami. Aby przekazać ten obiekt do naszych mocków, musimy podczas ich tworzenia, ustawić ten obiekt. Jak? Widzimy to w linii 8smej. Powtarzamy to dla każdego mocka, który nas interesuje.

Dalej standardowo. Za pomocą when() konfigurujemy Mock’a, injectujemy zaleznosci (linie 17-19) wywołanie metody testowej i assercje. Dzięki takiemu Testu możemy otrzymać interesujący output:

########### Logging method invocation #1 on mock/spy ########
userFactory.createUser("login", "password");
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.mockinvocations.AccountServiceTest.shouldCreateAccount(AccountServiceTest.java:31)
   has returned: "null"

############ Logging method invocation #2 on mock/spy ########
strengthPasswordValidator.validate(
    "password"
);
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.createAccount(AccountService.java:19)
   has returned: "0" (java.lang.Integer)

############ Logging method invocation #3 on mock/spy ########
userDao.findUserByLogin("login");
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.existAccount(AccountService.java:43)
   has returned: "null"

############ Logging method invocation #4 on mock/spy ########
   stubbed: -> at com.blogspot.mstachniuk.unittestpatterns.service.mockinvocations.AccountServiceTest.shouldCreateAccount(AccountServiceTest.java:31)
userFactory.createUser("login", "password"); 
   invoked: -> at com.blogspot.mstachniuk.unittestpatterns.service.AccountService.createAccount(AccountService.java:23)
   has returned: "User{login='login', password='password'}" (com.blogspot.mstachniuk.unittestpatterns.domain.User)
Pierwsze wywołanie, to te w teście:
when(userFactory.createUser(LOGIN, PASSWORD))
        .thenReturn(new User(LOGIN, PASSWORD));
Niestety (a może stety) nie można ukryć tego wywołania w standardowym outpucie, chyba że się napisze własna implementację InvocationListener’a.

W wywołaniu #2 widzimy, że wywołano metodę validate() z parametrem "password" i zwrócono zero. Co prawda metoda ta zwraca void, ale to pierdoła i nie należy się nią przejmować. Co fajniejsze, to po kliknięciu na AccountService.java:19 zostaniemy przeniesieni w miejsce wywołania tej metody. Bardzo użyteczny feature.

Trzecia interakcja jest analogiczna, ale za to czwarta jest ciekawa. Widzimy z jakimi parametrami wywołano mockowaną metodę, z którego miejsca w kodzie i gdzie zostało to zachowanie zdefiniowane! Od razu można przejrzeć konfigurację testu i się zorientować co skąd się bierze. Jak komuś za mało / za dużo, to zawsze można samemu sobie zaimplementować InvocationListener'a. Dzięki temu można trochę lepiej poznać bebechy tej biblioteki.

To tyle odnośnie tej logowania interakcji w Mockito. Obyście musieli jej używać jak najrzadziej, gdyż jak dla mnie świadczy to o skomplikowaniu i zagubieniu we własnych kodzie. Warto jednak o czymś takim zawczasu wiedzieć, może czasem uratować tyłek. Mi dzięki temu udało się dojść co było nie tak.

Cały kod wykorzystywany w tym wpisie można ściągnąć z githuba, a prezentowany kod testowy znajduje się w klasie: AccountServiceTestWithMockInvocations.

Morał tej historii:
Zwracaj uwagę na to co i jak mówią inni i nie daj się zmanipulować! Zwłaszcza w pracy :)

sobota, 22 września 2012

Kolejny Code Retreat

Dzisiaj wziąłem po raz kolejny udział w imprezie spod znaku Code Retreat. Tym razem odbył się on w Wolfsburgu – czyli mieście największego europejskiego koncernu samochodowego. Zorganizowany został on przez znajomych z pracy, a sponsorem była firma T-Systems on site.

Podczas początkowych sesji mogłem podzielić się swoją wiedzą z zakresu programowania w parach, TDD, baby steps, pisania dobrych testów i znajomości IDE. Nie ma się tu co rozpisywać, bo ciekawsze działo się później.

Postanowiłem wykorzystać obecność fanatyków Scali i trochę się poduczyć tegoż języka. Zdobyta wiedza przyda się przy realizacji kursu: Functional Programming Principles in Scala. Kto się jeszcze nie zapisał, ma jeszcze szansę. Więcej o kursie można poczytać na scala.net.pl. Podczas mojej pierwszej scalowej sesji, udało mi się sporo samemu napisać i odświeżyć jakieś stare wiadomości na temat tego języka. Część wymagań nawet działała. Szkoda jednak, że Eclipse dalej kuleje w tym temacie. Mam nadzieję, że z Ideą będzie lepiej.

Podczas mojej drugiej sesji skalowej programowałem razem z jeszcze większym wyjadaczem (fanatykiem) tego języka. Było więc co chłonąć. Podczas tej sesji (w ogólności) ćwiczyliśmy pisanie bez pętli, co w Scali nie jest jakimś utrudnieniem. Udało nam się całą logikę zakodować wraz z testami i bez pętli, w czasie krótszym niż 45 minut i z wyjaśnieniem mi, jak działa ta cała magia! Co chwilę wlepiałem swoje ślepia w ekran, dziwiąc się, co ‘to’ lub ‘tamto’ robi, oraz próbując zrozumieć, czy to aby na pewno pokrywa wszystkie wymagania. Jestem pod bardzo dużym wrażeniem tej sesji. Teraz trzeba na spokojnie (albo pod wpływem ;) ) poznać wykorzystane mechanizmy tegoż języka, aby móc je później efektywnie stosować.

Wyniesione doświadczenie z tej sesji, pozwoliło mi jeszcze podczas ostatniego sparowania, ponownie zakodować wszystkie reguły gry. Czasem lubię się droczyć, z moimi partnerami podczas Code Retreat. Zwłaszcza przy ping pongu staram się tak odbić piłeczkę, aby się później dużo nie narobić, a jedynie obserwować, jak ktoś się męczy. Tym razem coś poszło nie tak, gdyż sam musiałem się napocić. O dziwo udało się po raz drugi dojść do końca, bez większych problemów, że coś nie działa i nie wiadomo dlaczego. Dodatkowo nie można było rozmawiać podczas tej sesji, co stanowiło dodatkowe utrudnienie. Spotkałem się z nim po raz pierwszy i przekonałem się, ile rzeczy można przekazać niewerbalnie.

Podsumowując wypadło super. Był to mój 6ty CodeRetreat (licząc te oficjalne jak i te zamknięte). Obiad był na wypasie, ciepły z dużymi możliwościami wyboru dania i z deserem. Również było sporo owoców (zamiast ciasteczek) i możliwość zjedzenia śniadania (jak ktoś nie zdążył rano). Ważnym punktem (który był strzałem w dziesiątkę) była Club Mate. Pomogła ona zebrać siły na ten jakże wyczerpujący wysiłek intelektualny, jakim jest Code Retreat. Nieźle się naYerbałem, a w ogólnym rozrachunku, to cała skrzynka poszła. Uważam, że napój ten, powinien wejść do formuły Code Retreat ;)

Teraz pozostaje już tylko czekać, na drugi Global Day of Coderetreat, który w tym roku przypada 8 grudnia. Rok temu 94 miasta wzięły w tym udział, a cel na ten rok to 200 miast. Zobaczymy czy się uda i jak spędzę ten dzień.

piątek, 17 sierpnia 2012

Cuda na Javie część 2

W poprzednim wpisie CUDA na Javie opisałem trochę walki z technologią i ze sprzętem, dotyczącym programowania CUDA za pomocą Javy. Bibliotekę jCUDA odrzuciłem ze względu na brak ogólnodostępnej dokumentacji i możliwości rejestracji się, aby się dobrać do tej dokumentacji. Skorzystałem więc z konkurencyjnego rozwiązania: JCuda w wersji 0.3.2a dla CUDA 3.2 (jako że z nowszą wersją nie mogłem się dogadać). Czeka nas jeszcze doinstalowanie czegoś i konfiguracja, ale na początek trochę teorii.

Chcąc pisać w CUDA'ch, tworzymy pliki tekstowe z rozszerzeniem *.cu. Te następnie są on kompilowane z pomocą nvcc (kompilatora dostarczanego przez Nvidię w CUDA Toolkit) do jednego z kilku formatów: (np. PTX, CUBIN, EXE). Pierwszy z nich zawiera instrukcje asemblerowe, które są czytelne dla ludzi (jak ktoś oczywiście lubi / umie). Jest to również uniwersalny format, gdyż przed wykonaniem takiego kodu, jest on kompilowany pod konkretny układ GPU. Natomiast format CUBIN, to już format binarny pod konkretny procesor.

Więcej szczegółów na temat procesu kompilowania i linkowania Cudy zamieszczam na poniższym rysunku (schemat ze strony: http://www.think-techie.com/2009/09/gpu-computing-using-jcuda.html):


W plikach *.cu możemy umieszczać typowy kod C jak i tzw. The Kernel Code. Jest to kod, który wykonuje się na urządzeniu (ang. device) obsługującym CUDA. Kompilator nvcc nie jest przez to samowystarczalny. Do jego poprawnego działania wymagany jest jeszcze kompilator C, np. Visual Studio C++. Początkowo zainstalowałem VC++ 2010 Express Edition, ale się okazało, że moja wersja CUDA współpracuje z MCVC 8.0 lub 9.0. Zainstalowałem więc Visual Studio 2008 i przy próbie kompilacji przykładu Creating kernels dostałem następujący błąd:

nvcc fatal   : Cannot find compiler 'cl.exe' in PATH

Ok, szukamy cl.exe I dodajemy do PATH, czyli ***\Microsoft Visual Studio 9.0\VC\bin.

Teraz robi się ciekawie. Uruchamiając przykład, dostaję:
nvcc -m64 -ptx JCudaVectorAddKernel.cu -o JCudaVectorAddKernel.ptx
nvcc process exitValue -1
errorMessage:
nvcc fatal   : Cannot find compiler 'cl.exe' in PATH

ale uruchamiając to samo „z palca” dostaję:

nvcc fatal: Visual Studio configuration file '(null)' could not be found for installation at...

Musiałem doinstalować X64 Compilers and Tools



I zaczęło śmigać.

Czasem może być jeszcze wymagane dodanie do PATH’a: ***\Microsoft Visual Studio 9.0\VC\bin\amd64

Można również się natknąć na poniższy problem:

host_config.h(96) : fatal error C1083: Cannot open include file: 'crtdefs.h': No such file or directory

Aby go rozwiązać, trzeba jeszcze wskazać CUDA, gdzie są pliki nagłówkowe z kompilatora C. Aby to zrobić, należy w pliku nvcc.profile (w katalogu bin instalacji CUDA) w sekcji INCLUDES dodać ścieżkę do odpowiedniego katalogu:

INCLUDES        +=  "-I$(TOP)/include" "-I$(TOP)/include/cudart" "-Id:/Microsoft Visual Studio 9.0/VC/include" $(_SPACE_)

I już działa. Rozwiązanie ostatniego problemu znalazłem tutaj: http://blogs.hoopoe-cloud.com/index.php/2009/09/cudanet-examples-issues

Skoro już wszystko działa (mam nadzieję że niczego nie ominąłem) czas na jakiś kod. Sztandarowym przykładem pokazującym potęgę GPU jest sumowanie wektorów. Poniżej przedstawię przykład ze strony JCuda Code samples. Musiałem jednak trochę go zmodyfikować, gdyż tamtejszy przykład jest przygotowany dla cudów 4.0 i korzysta z metod, których w mojej wersji API (0.3.2a) nie ma. Podrasowałem również kod samego kernela, aby wyjaśnić pewne zjawiska tam zachodzące. Kod biblioteki jest na licencji MIT/X11 License więc mogę modyfikować, z zachowaniem informacji o autorze.

Zacznijmy od kodu Javowego:

// Enable exceptions and omit all subsequent error checks
JCudaDriver.setExceptionsEnabled(true);

// Create the PTX file by calling the NVCC
String ptxFileName = preparePtxFile("JCudaVectorAddKernel.cu");

// Initialize the driver and create a context for the first device.
cuInit(0);
CUdevice device = new CUdevice();
cuDeviceGet(device, 0);
CUcontext context = new CUcontext();
cuCtxCreate(context, 0, device);

// Load the ptx file.
CUmodule module = new CUmodule();
cuModuleLoad(module, ptxFileName);

// Obtain a function pointer to the "add" function.
CUfunction function = new CUfunction();
cuModuleGetFunction(function, module, "add");

Mamy tutaj trochę rzeczy związanych z ustawieniem sterownika Cuda. Początkowo jest włączane rzucanie wyjątków z metod biblioteki, gdy coś pójdzie nie tak. Samo już istnienie takiej metody świadczy według mnie o autorze, któremu jest bliżej do C niż do Javy. Moje obawy zostały potwierdzone przez początkowe formatowanie kodu w źródłowym pliku.

Następnie w linii 5tej ładujemy nasz plik *.cu, a dokładniej, jeżeli nie istnieje dla niego odpowiednik w formacie PTX, to nasz plik źródłowy z kodem kelnera jest kompilowany, w trakcie działania naszej aplikacji. Jest to metoda napisana przez autora przykładu i jak ktoś chce się jej przyjrzeć bliżej to niech zajrzy do kodu.

Wracając do opisanego przykładu dalej następuje inicjalizacja naszego urządzenia i utworzenie dla niego kontekstu. Dalej w linii 15stej nastepuje utworzenie modułu i załadowanie do niego skompilowanego pliku PTX, zwróconego przez metodę preparePtxFile(). W ostatniej linijce w końcu dobieramy się do naszej funkcji, którą napisaliśmy w naszym pliku *.cu.

Dobra teraz czas na przygotowanie danych wejściowych do naszej funkcji:

int numElements = 100000;

// Allocate and fill the host input data
float hostInputA[] = new float[numElements];
float hostInputB[] = new float[numElements];
for (int i = 0; i < numElements; i++) {
    hostInputA[i] = (float) i;
    hostInputB[i] = (float) i;
}

Tworzymy dwa wektory o długości 100k i wypełniamy kolejnymi liczbami. Zabieg ten ma na celu łatwą weryfikację poprawności uzyskanego wyniku. Stworzone dane wejściowe zostaną utworzone na stercie i nie będą dostępne dla GPU. Po za tym odczyt ten mógłby być bardzo długi (z powodu prędkości magistrali, obecności pamięci wirtualnej i stronicowania). Dla tego względu należy dane przekopiować do obszaru urządzenia. Robotę wykonuje poniższy kod:

// Allocate the device input data, and copy the
// host input data to the device
CUdeviceptr deviceInputA = new CUdeviceptr();
cuMemAlloc(deviceInputA, numElements * Sizeof.FLOAT);
cuMemcpyHtoD(deviceInputA, Pointer.to(hostInputA),
        numElements * Sizeof.FLOAT);
CUdeviceptr deviceInputB = new CUdeviceptr();
cuMemAlloc(deviceInputB, numElements * Sizeof.FLOAT);
cuMemcpyHtoD(deviceInputB, Pointer.to(hostInputB),
        numElements * Sizeof.FLOAT);

// Allocate device output memory
CUdeviceptr deviceOutput = new CUdeviceptr();
cuMemAlloc(deviceOutput, numElements * Sizeof.FLOAT);

W linii 3ciej tworzymy wskaźnik, który może wskazywać na pamięć urządzenia. Następnie alokujemy do tego wskaźnika pamięć o wielkości numElements * Sizeof.FLOAT. Pierwsza wartość oznacza długość wektora, a druga wielkość zajmowanej pamięci przez jeden element. Sizeof.FLOAT wynosi 4 (bajty).

Następnie w linii 5tej i 6tej następuje kopiowanie ze „zwykłej” pamięci (host) do urządzenia (device), czyli metoda cuMemcpyHtoD(). Najpierw jako argument podajemy wskaźnik do pamięci urządzenia, później referencję dla naszego wektora z pamięci hosta. Na koniec jeszcze ilość bajtów, jaką należy skopiować. Analogiczne operacje są powtarzane dla drugiego argumentu wejściowego. Na koniec jest jeszcze alokowana pamięć na urządzeniu, gdzie będzie zapisywany wynik.

Dobra czas na przekazanie argumentów do funkcji. W nowszym API można to zrobić bardziej przyjemniej. Ja musiałem się zapytać na forum, co z tym fantem, ale na szczęście autor szybko odpisał. Rozwiązanie dla wersji 0.3.2a poniżej:

// sets parametrs
int offset = 0;

offset = align(offset, Sizeof.INT);
cuParamSetv(function, offset, Pointer.to(new int[]{numElements}), Sizeof.INT);
offset += Sizeof.INT;

offset = align(offset, Sizeof.POINTER);
cuParamSetv(function, offset, Pointer.to(deviceInputA), Sizeof.POINTER);
offset += Sizeof.POINTER;

offset = align(offset, Sizeof.POINTER);
cuParamSetv(function, offset, Pointer.to(deviceInputB), Sizeof.POINTER);
offset += Sizeof.POINTER;

offset = align(offset, Sizeof.POINTER);
cuParamSetv(function, offset, Pointer.to(deviceOutput), Sizeof.POINTER);
offset += Sizeof.POINTER;

Jako pierwszy argument przekazujemy do funkcji długość naszego wektora. Jest to liczba typu int i w linii 5tej właśnie ustawiamy, że numElements będzie przekazywane jako pierwszy argument. Jako że nie możemy utworzyć Pointer’a do pojedynczej zmiennej, trzeba zrobić na szybko jednoelementową tablicę i przekazać jako rozmiar wielkość int’a.

Od razu liczony jest offset, który w ostatniej metodzie danego listingu jest przekazywany do funkcji. Pewnie to jest po to, aby mechanizm wywołujący metodę, wiedział ile danych trzeba odczytać ze stosu (moje podejrzenie).

Dobra, mamy już dane wejściowe, mamy je przyporządkowane jako argument funkcji, najwyższy czas na wywołanie:

// Call the kernel function.
int blockSizeX = 256;
int gridSizeX = (int) Math.ceil((double) numElements / blockSizeX);

cuParamSetSize(function, offset);

// run grid
cuLaunchGrid(function, gridSizeX, blockSizeX);
cuCtxSynchronize();

Ustalamy wielkość bloku i gridu, który posłuży nam do obliczeń, przekazujemy długość parametrów i wywołujemy funkcję. O co chodzi z blokami i gridami to możecie doczytać w książce CUDA w przykładach  lub w CUDA C Programming Guide (obrazek 2-1, strona 9) który jest również dostępny po zainstalowaniu CUDA Toolkit.

Metodę cuCtxSynchronize() wywołujemy po to, aby w razie niepowodzenia jak najszybciej się dowiedzieć, gdzie mniej więcej jest błąd. Pozostaje jeszcze skopiowanie wyniku:

// Allocate host output memory and copy the device output
// to the host.
float hostOutput[] = new float[numElements];
cuMemcpyDtoH(Pointer.to(hostOutput), deviceOutput,
        numElements * Sizeof.FLOAT);

do RAMu, porównanie wyników:

// Verify the result
boolean passed = true;
for (int i = 0; i < numElements; i++) {
    float expected = i + i;
    if (Math.abs(hostOutput[i] - expected) > 1e-5) {
        System.out.println(
                "At index " + i + " found " + hostOutput[i] +
                        " but expected " + expected);
        passed = false;
        break;
    }
}
System.out.println("Test " + (passed ? "PASSED" : "FAILED"));

i sprzątanie:

// Clean up.
cuMemFree(deviceInputA);
cuMemFree(deviceInputB);
cuMemFree(deviceOutput);

No to czas na kod wykonywany na GPU:

extern "C"
__global__ void add(int n, float *a, float *b, float *sum)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    while(i < n) {
      sum[i] = a[i] + b[i];
      i = i + blockDim.x * gridDim.x;
    }
}

Linijkę extern "C", potrzebujemy, aby kompilować naszą funkcję zgodnie z regułami kompilowania dla ANSI C. Następnie __global__ powoduje, że metoda oznaczona takim kwalifikatorem, będzie się wykonywać na GPU i może być wywołana z poziomu hosta (CPU). Inna użyteczna definicja to __device__. Funkcje oznaczone w ten sposób, również działają na GPU, ale mogą jedynie być wywołane z poziomu innych funkcji GPU.

Dalej mamy sygnaturę metody, czyli przekazujemy kolejno długość wektorów i wskaźniki na wektory wejściowe i jeden na wyjściowy. Kod jest podobny do tego przedstawionego w CUDA w przykładach (str. 66). W pierwszej linijce funkcji wyliczamy indeks, od którego będziemy zaczynać sumowanie. blockDim reprezentuje liczbę wątków w każdym wymiarze bloku. My stosujemy tylko jednowymiarowy blok. W blockIdx znajduje się indeks bloku, który aktualnie wykonuje kod danej funkcji, a w threadIdx indeks aktualnego wątku. Są to 3 standardowe zmienne systemu wykonawczego CUDA. Siatki bloków mogą być dwuwymiarowe, a każdy blok może mieć trójwymiarową tablicę wątków. Więc jak się ktoś orientuje w pięciu wymiarach, to ma pole do popisu.

Dalsze obliczenia (pętla while) wykonujemy od póki indeks jest mniejszy od długości zadanego wektora. W linijce 6tej nic się ciekawego nie dzieje (po prostu jest obliczany wynik) i w następnie obliczamy kolejny indeks. Normalnie (w przypadku implementacji jednowątkowej) to byśmy wykonywali inkrementację, ale jako że kod jest wykonywany na GPU, to kolejny indeks musimy trochę inaczej policzyć. Zwiększamy go o iloczyn liczby wątków na blok i liczby bloków w siatce.

Na razie to tyle co przygotowałem o cudach. Kod znajdziecie na githubie (JCudaExample), choć nie jest on nie wiadomo jak odkrywczy. Przy uruchomieniu mogą pojawić się kłopoty, więc warto dopasować ścieżkę (java.library.path) w konfiguracji uruchomieniowej.

Podsumowując, jeśli mamy sporo równoległych obliczeń do wykonania, dobrą kartę graficzną i zależy nam na szybkim czasie działania, to można się pobawić w Cuda. Nie jest to jednak tak przyjemnie jak JVM, gdzie wiadomo, z której linijki leci wyjątek i wszystko staje się szybciej jasne. Mimo wszystko praca ze sprzętem daje dużo satysfakcji.

środa, 15 sierpnia 2012

CUDA na Javie

Poniższy artykuł nie będzie ani o zjawiskach paranormalnych, „interwencji istot nadprzyrodzonych” (patrz: cud na Wikipedii) ani o śnieniu „na jawie” ;) Mianowicie CUDA (ang. Compute Unified Device Architecture) to opracowana przez Nvidię „uniwersalna architektura procesorów wielordzeniowych” występująca głównie na kartach graficznych. Architektura ta ma więc wspomagać rozwiazywanie skomplikowanych problemów numerycznych. Stworzono do tego framework OpenCL, który jest oparty na języku C (a dokładniej na C99). Jak słusznie zauważył Bartek, OpenCL jest osobną, alternatywą dla Cudy i nie będę się nią tutaj zajmował.

Przeglądając angielską wiki na temat CUDA, zauważyłem, że są dostępne 4 biblioteki dla Javy, współpracujące z technologią Nvidi: jCUDA, JCuda, JCublas, JCufft. Jako że moja karta graficzna (GeForce 9600M GT) w laptopie obsługuje CUDA w wersji 1.1 (aktualne dane można sprawdzić na stronie CUDA GPUs), postanowiłem trochę się pobawić tymi klockami i sprawić aby się choć trochę spociła.

Przyglądając się bliżej tym czterem bibliotekom Javowym, okazało się, że tak właściwie są to 2 projekty: jCUDA i JCuda. Pozostałe: JCublas i JCufft są de facto podprojektami JCuda. JCublas zajmuje się operacjami wektorowymi i macierzowymi. Natomiast JCufft zajmuje się szybką transformacją Fouriera. Ten kto kończył studia na Wydziale Elektroniki PWr, lub coś co jest związane z elektroniką, to wie o co chodzi ;) Druciarze rządzą!


Ściągnąłem więc na początek jCUDA wersję na Windowsa 64 bit.

Następnie utworzyłem projekt w IntelliJ Idea, skopiowałem zawartość katalogu examples (ze ściągniętego ZIP’a ) do src, a jcuda.jar, jcuda.dll i jcudafft.dll do lib i dodałem do projektu ze scope’m: "Compile"



Pierwsze uruchomienie klasy EnumDevices (widzę że jest tam jakiś main()) i dostajemy stacktrace’a:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no jcuda in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)
 at jcuda.driver.CUDADriver.(CUDADriver.java:909)
 at jcuda.CUDA.init(CUDA.java:62)
 at jcuda.CUDA.(CUDA.java:42)
 at examples.EnumDevices.main(EnumDevices.java:20)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Myślałem początkowo, że to podczas uruchomienia, aplikacja nie widzi dołączanych JAR’ów. Zmiana scope’a na Runtime, powoduje jednak, że IDEA nie widzi biblioteki podczas kompilacji:


Wróciłem wiec do zasięgu compile. Nie chcąc grzebać sobie zbytnio w classpath’ie, ustawiłem właściwość java.library.path w konfiguracji uruchomieniowej wspomnianej klasy na katalog z wymaganymi dll’kami:



Po ponownym uruchomieniu otrzymałem ten oto stos wywołań:

Exception in thread "main" java.lang.UnsatisfiedLinkError: D:\Java_programy\IdeaWorkspace\jCUDAFirstExample\lib\jcuda.dll: Can't load AMD 64-bit .dll on a IA 32-bit platform
 at java.lang.ClassLoader$NativeLibrary.load(Native Method)
 at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1803)
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1728)
 at java.lang.Runtime.loadLibrary0(Runtime.java:823)
 at java.lang.System.loadLibrary(System.java:1028)
 at jcuda.driver.CUDADriver.(CUDADriver.java:909)
 at jcuda.CUDA.init(CUDA.java:62)
 at jcuda.CUDA.(CUDA.java:42)
 at examples.EnumDevices.main(EnumDevices.java:20)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Hmmm, wersje systemu mam 64 bitową, ale nie na procku AMD tylko Intel Core 2 Duo T6400 2.00 GHz.

Dobra ściągamy wersję 32 bitową na Winde. Podmiana plików, przebudowa projektu i Voila:

Total number of devices: 1
Name: GeForce 9600M GT
Version: 1.1
Clock rate: 1250000 MHz
Threads per block: 512

W przykładowym kodzie znajduje się jeszcze klasa LoadModule, uruchamiam więc:

Exception in thread "main" CUDA Driver error: 301
 at jcuda.CUDA.setError(CUDA.java:1874)
 at jcuda.CUDA.loadModule(CUDA.java:375)
 at examples.LoadModule.main(LoadModule.java:36)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

I idę do linii gdzie poleciał wyjątek:

File cubinFile = new File("resources", "test_module.cubin");
cuda.loadModule(cubinFile.getAbsolutePath());

W archiwum z biblioteką jest jeszcze katalog resource, wiec kopiuję go do projektu i dołączam jako zasób:



Od razu nowe skróty klawiaturowe do zapamiętania, dostępne w oknie Project Structure - Modules - dodaj jako: Alt + S Sources, Alt + T Test Sources, Alt + E Excluded.

I otrzymujemy miły komunikat po uruchomieniu:

Test passed

Z ciekawości jeszcze zajrzałem co się znajduje w podpakiecie driver przykładowego kodu. Po uruchomieniu okazuje się, że aplikacje wypisują to samo na ekran. Porównałem kdiff3’em, co się zmienia w kodzie. Jak można przeczytać w release note w pliku readme.txt od wersji 1.1 biblioteki wprowadzono bardziej obiektowy sposób korzystania z biblioteki, aby nam Javowcom było łatwiej pracować :)

Added object oriented support for jCUDA and CUDA interface.

Niestety aby dowiedzieć się coś więcej o tej bibliotece, trzeba się zarejestrować na stronie producenta. Próbowałem z dwóch kont (gmail’owe i studenckie), ale zero odzewu. Później się doszukałem, że rejestracja jest tymczasowo nieaktywna i ze wyślą e-mail’a aktywacyjnego, gdy rejestracja będzie znów dostępna…

Jako, ze nie chcę pracować z biblioteką do której nie ma ogólnodostępnej dokumentacji, a rejestracja nic nie daje, wziąłem  się za drugą dostępną bibliotekę JCuda. Nauczony powyższym doświadczeniem od razu ściągnąłem wersję 4.0 32 bitową, dokumentację (są javadoc’i) i kod źródłowy. Początkowo postanowiłem przejrzeć przykłady: JCuda Code samples.

Zacząłem od pierwszego JCublasSample. Ściągnąłem, skopiowałem do src i to co było w ZIPie z JCudą skopiowałem do lib w moim projekcie. Następnie dodałem je jako zależności z zakresem Compile.


Pierwsze uruchomienie i oczywiście stacktrace:

Creating input data...
Performing Sgemm with Java...
Performing Sgemm with JCublas...
Error while loading native library "JCublas-windows-x86_64" with base name "JCublas"

Operating system name: Windows 7
Architecture         : amd64
Architecture bit size: 64
Stack trace from the attempt to load the library as a resource:
java.lang.NullPointerException: No resource found with name '/lib/JCublas-windows-x86_64.dll'
 at jcuda.LibUtils.loadLibraryResource(LibUtils.java:144)
 at jcuda.LibUtils.loadLibrary(LibUtils.java:80)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Stack trace from the attempt to load the library as a file:
java.lang.UnsatisfiedLinkError: no JCublas-windows-x86_64 in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)
 at jcuda.LibUtils.loadLibrary(LibUtils.java:90)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Exception in thread "main" java.lang.UnsatisfiedLinkError: Could not load the native library
 at jcuda.LibUtils.loadLibrary(LibUtils.java:122)
 at jcuda.jcublas.JCublas.initialize(JCublas.java:94)
 at jcuda.jcublas.JCublas.(JCublas.java:82)
 at JCublasSample.sgemmJCublas(JCublasSample.java:64)
 at JCublasSample.testSgemm(JCublasSample.java:49)
 at JCublasSample.main(JCublasSample.java:25)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Process finished with exit code 1

Z czego interesujące jest to:

no JCublas-windows-x86_64 in java.library.path

problem rozwiązuję jak w przypadku pierwszej biblioteki, czyli ustawiam właściwość java.library.path dla wirtualnej maszyny Javy w konfiguracji uruchomieniowej przykładu. Uruchamiam i ten sam błąd. Przyglądam się bliżej nazwie brakującego pliku: JCublas-windows-x86_64. Okazuje się, że w wskazanym katalogu mam taki plik ale bez przyrostka _64, czyli pewnie framework, na podstawie wyświetlanej wartości „Architecture: amd64” poszukuje automatycznie bibliotek 64bitowych. No dobra, ściągnąłem, podmieniłem, uruchamiam i znów myślałem, że dostałem to samo.

Jednak gdy się bliżej przyjrzałem, to się okazało ze jest trochę odmienny:

java.lang.UnsatisfiedLinkError: D:\Java_programy\IdeaWorkspace\JCudaExample01\lib\JCublas-windows-x86_64.dll: Can't find dependent libraries

Zajrzałem więc do JCuda Tutorial’a i tamta strona zaprowadziła mnie do JCuda FAQ na jakimś formu:

Type 2: UnsatisfiedLinkError: Can't find dependent libraries

Czyli jednak czegoś nie doinstalowałem związanego bezpośrednio z CUDĄ. Ściągnąłem więc CUDA Toolkit (64 bit) zainstalowałem i uruchomiłem ponownie komputer. Po odpaleniu aplikacji dostałem:

Creating input data...
Performing Sgemm with Java...
Performing Sgemm with JCublas...
testSgemm FAILED

Już jest lepiej, ale chyba coś dalej jest nie tak. Zajrzałem w kod i się okazało, że przykładowa aplikacja korzysta z biblioteki JCublas, a ta wykorzystuje cuBLAS (ang. CUDA Basic Linear Algebra Subroutines), czyli bibliotekę wspomagającą podstawowe obliczenia algebry liniowej. Nie chciało mi się zgłębiać co jest nie tak i uruchomiłem inny przykład.

Klasa JCufftSample wymaga do uruchomienia dodatkowo javovej biblioteki JTransforms, która to liczy m.in. transformatę Fouriera. Uruchomienie tego przykładu wykrywało w moim przypadku architekturę 32 bitową:

Operating system name: Windows 7
Architecture         : x86
Architecture bit size: 32

No i oczywiście sypało wyjątkami. Postanowiłem więc w katalogu lib trzymać zarówno 64 bitowe jaki 32 bitowe dll’ki. Prawdopodobnie dzięki temu udało mi się wywalić wirtualną maszynę:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0ad2de60, pid=4376, tid=6112
#
# JRE version: 6.0_22-b04
# Java VM: Java HotSpot(TM) Client VM (17.1-b03 mixed mode, sharing windows-x86 )

Trochę zniechęcająca jest praca z tymi bibliotekami. Ale tak to zazwyczaj jest, jak się bawimy ze sprzętem, że na początku bardzo ciężko się z nim dogadać, a później to już jakoś lepiej idzie.

Jako że początkowo korzystałem z bibliotek w wersji 0.4.0-beta1 dla CUDA 4.0 postanowiłem zrobić downgrade do JCuda 0.3.2a. dla CUDA 3.2. Taka konfiguracja sprawia mi najmniej problemów. JCufftSample zaczął działać, i najprostszy, podstawowy test z tutorial’a też.
Proponuję wszystkim zaczynać od tego przykładu, gdyż ten test alokuje pewną pamięć w przestrzeni GPU i ją zwalnia – nic więcej.

Teraz to by było na tyle odnośnie cudów. Kolejne porady, co jeszcze należy doinstalować i jak przygotować środowisko do pracy z tym wynalazkiem w kolejnej części.

No i przepraszam za rozjechane listingi kodu pod Chrome'm, ale coś SyntaxHighlighter się pod tą przeglądarką rozjeżdża.

piątek, 6 lipca 2012

Konfitura, marmolada, dżem mydło i powidło ‘12


W ostatnią sobotę odbyła się największa (pod względem ilości uczestników ~850) polska, jednodniowa, konferencja javowa, czyli Confitura 2012. Tym razem odbywała się ona w budynkach Uniwersytetu Warszawskiego na Krakowskim Przedmieściu.

Pierwszy wykład był sponsorowany przez Akamai, tytuł miał po angielsku, więc spodziewając się zła koniecznego (czyli typowej reklamy jakiegoś produktu, za którą ktoś sporo zapłacił) odpuściłem sobie ten wykład. Pozwiedzałem wówczas stanowiska sponsorskie, zgarniając co się dało. Miałem również możliwość porozmawiania z pracownikami firmy e-point, którzy ostatnio wypuścili OneWebSQL. Jest to framework, tworzący na podstawie modelu danych, klasy encji, DAOsy i tak dalej. Taki ORM, tylko że dostajemy wygenerowany kod, którego ręcznie nie modyfikujemy, a jedynie wywołujemy to co nam jest potrzebne. Widziałem wcześniej filmik prezentujący to rozwiązanie, na którym wykorzystywano PowerDesigner’a do przygotowania modelu danych. Dowiedziałem się, że wsparcie dla kolejnych narzędzi jest w drodze, w tym generowanie kodu na podstawie istniejącego schematu bazy. Czy odniesie to sukces komercyjny - zobaczymy.

Po godzinie 10 była prezentacja Grześka Dudy na temat produktywności. Okazuje się, że najbardziej produktywni jesteśmy do kilku godzin po przebudzeniu i powinniśmy ten czas jak najlepiej spożytkować. Powinniśmy wtedy kodować, analizować problemy i wszystko to, co wymaga od nas dużego wysiłku intelektualnego. Nie powinniśmy wtedy czytać e-maili, oglądać YT ani grać w gry.

Kolejna sprawą jest podział zadań do wykonania na:
1) ważne i pilne
2) ważne i niepilne
3) nieważne i pilne
4) nieważne i niepilne.
O podziale tym można chyba przeczytać w każdej książce dla menagierów i na temat zarządzania sobą w czasie.

Kolejna sprawa to automatyzacja wszystkiego co się da. I Grzesiek nie miał tutaj na myśli automatycznych testów (bo to jest jasne ze to trzeba robić) ale optymalizację powtarzających się co jakiś czas czynności. Przykładem jest test Selenium, który może coś tam aktualizować w Eclipse.

Powinniśmy również unikać robienia 2ch rzeczy równocześnie, chyba że angażujemy do tego 2 półkule mózgowe. Inaczej nie wpływa to dobrze na wydajność. Część małych rzeczy możemy zrobić, czekając na przystanku na autobus, jak np. zadzwonić do mamy.

Następnie Grzesiek krytykował Pomodoro Technique. Uważa on, że 25 min. to za mało. W tym czasie dopiero się rozkręcamy i nie zdążymy zrobić nic konkretnego. Jedyny sens robienia pomidora, to notowanie co nam przeszkadza i jak często. Przydatne tutaj mogą być również narzędzia do analizy, która aplikacja jak długo jest otwarta, np. RescueTime. Inna ciekawa aplikacja to Pocket (Formerly Read It Later), do zapamiętywania tego, co chcemy przeczytać (aby zminimalizować liczbę otwartych zakładek w przeglądarce). Zacząłem ją od teraz stosować. Przydaje się zwłaszcza, gdy w pracy są niektóre strony poblokowane.

Gdy już przeanalizujemy, co nam przeszkadza w pracy, to najczęściej okazuje się, że są to e-maile. Dobrze jest więc wyłączyć wszelkie powiadomienia o nowych wiadomościach, aby nie wychodzić z flow. Potwierdzam z doświadczenia, że to pomaga. Kolejna powiązana kwestia to moment odpisywania na e-maile. Zasada three.sentenc.es mówi, że jeśli jesteśmy w stanie odpisać w 3ch zdaniach, to można zrobić to zaraz po przeczytaniu wiadomości. Jeśli mamy więcej do napisania, to lepiej pogadać. No i oczywiście pisząc bezwzrokowo jesteśmy w stanie zyskać 30% na produktywności (w dłuższym okresie czasu).

Jeśli chodzi o oprawę organizacują, to całkiem fajnie się oglądało prezentację z góry, tj. z balkonu. Niestety nie wiedziałbym o tej możliwości, gdyby Tomek Dziurko (jeden z organizatorów) mi o tym nie powiedział. Ogółem brakowało mi trochę oznaczeń gdzie co jest. Przykładowo dopiero ze zdjęć się dowiedziałem że gdzieś były stare konsole (a może to i lepiej że nie wiedziałem). Następnym razem proszę o więcej oznaczeń, lub schematyczny rzut z góry z zaznaczonymi ciekawymi miejscami.

Następnie byłem na Kędzierzawych testach z kontraktami Cezarego Bartoszuka. Prelegent prezentował bibliotekę CoFoJa. Pomysł kontraktów nie jest nowy, ale pomimo czasu nie przyjął się. Dla mnie te kontrakty to bardziej ciekawostka, niżeli coś co chciałbym stosować. Przypominają mi one słówko kluczowe assert, gdyż tak jak asserta kontrakty można wyłączyć, a korzystać z nich powinniśmy jedynie w trakcie testów. No i nie mamy wsparcia dla refaktoryzacji, a długość definicji pojedynczego pola w klasie mocno rośnie. Jak dla mnie lepiej wykorzystać TDD i BeanValidation jak już coś musimy walidować.

Jeśli chodzi o kędzierzawość, to prelegent przedstawił jeszcze testy z Yeti Test. I to narzędzie już bardziej mi podeszło. Potrafi ono generować sporo losowych testów jednostkowych dla naszego kodu, z uczuleniem na błędne i skrajne wartości. Yeti dobrze współpracuje z kontraktami, ale można go oczywiście stosować oddzielnie.

Co do wad prezentacji można zaliczyć przepełnienie sali. Wiem że była wcześniej ankieta, na co kto chce iść i na tej podstawie były przydzielane sale, ale to przepełnienie świadczy o trochę za dużej liczbie uczestników całej konferencji.

Następnie udałem się na zwinne szacowanie Piotra Burdyło. Było o szacowaniu całości projektu, a nie pojedynczych zadań. Prelegent opowiadał o prawie Parkinsona i syndromie studenta.

Co do szacowania Piotrek zdefiniował kilka zasad, których się trzyma:

  • Szacuj po ludzku
  • Mierz rzeczy które są mierzalne
  • Różne szacunki wymagają różnych metryk
  • Bliska przyszłość - dużo szczegółów, daleka przyszłość - mało szczegółów

Prelegent czerpie też sporo z Agile Manifesto i twierdzi że weryfikacja jakości oszacowania jest bez sensu. Wynika to z częstego wypełniania zestawienia czasu pracy pod koniec miesiąca, a także z niedokładności tego wypełnienia. Bo co np. zrobić, gdy kolega z zespołu przyjdzie do nas po pomoc? Zatrzymać aktualny stoper i włączyć inny? No i gdzie de facto upychać wszelkie przeszkadzajki (e-maile, wypełnianie czasu pracy, inne korporacyjne pierdoły, piłkarzyki...)? Nie chce nam się tego aż tak dokładnie notować, gdyż nie jest to warte swego zachodu.

Na koniec była jeszcze ciekawa metoda szacowania całego projektu. Mianowicie piszemy na karteczkach nasze historyjki i je kolejno przyklejamy na tablicy. Kolejną karteczkę umieszczamy w miejscu, które będzie wskazywało, na ile nowa historyjka jest trudniejsza od poprzednich. Na koniec dzielimy tablicę na kilka części i przyporządkujemy historyjkom odpowiednie punkty. Te które są na granicy ponownie analizujemy czy raczej są większe czy mniejsze. Na koniec jeszcze sporo pytań, aż prelegent nie dotarł do końca prezentacji.

Następnie ponownie udałem się na prezentację miękką, tym razem odnośnie Retrospekcji autorstwa Michała Bartyzela. W sali tej był mały problem ze światłem, mianowicie było go za dużo, a przez to slajdy niewidoczne. Na szczęście prelegent więcej gadał niż pokazywał. Widać że Michał ma bardzo dobrze opanowaną sztukę publicznych wystąpień, swobodnie stoi na scenie i bardzo szybko dostosowuje się do aktualnych wydarzeń na sali. Przykładowo raz zaczął się witać ze spóźnialskimi, a gdy mówił o nowych postanowieniach, to jako przykład podał, aby się więcej nie spóźniać na prezentacje (oczywiście ktoś akurat wchodził na salę).

Prelegent zachęcał do robienia małych, osobistych retrospekcji codziennie. Ma to na celu wytworzenia w nas wyrzutów sumienia, które później powinny nas zmotywować do poprawy naszych małych grzeszków. No i odpowiedni powinniśmy definiować swoją pokutę, czyli nie "poprawię swoje szacowania", a "będę szacował za pomocą przedziałów".

Było jeszcze o tym czym jak nie powinny wyglądać retrospekcje. Nie jest to terapia grupowa, gdzie wylewamy swoje żale i idziemy zadowoleni do domu. Po retrospekcji powinny pójść konkretne ustalenia i zmiany.

Na koniec było jeszcze o pytaniach C5:
1. Co zacząć robić?
2. Co przestać robić?
3. Czego robić więcej?
4. Czego robić mniej?
5. Co robić inaczej?

Kolejny wykład na który się udałem był o tym jak uwolnić się od "if" Tomka Nurkiewicza. Są dwa powody dla którego powinniśmy się ich pozbywać. Poprzez duże zagęszczenie instrukcji warunkowych tracimy na czytelności i na szybkości (na poziomie CPU, ale to temat na osobny wpis).

Było sporo przykładów z kodem i refaktoringu na żywo. Bardzo często sprawdzamy za pomocą if'ow, czy pewne wartości nie są null'em. Zamiast tego lepiej stosować Null Object. Dodatkowo chcąc pozbyć się obsługi opcjonalnych argumentów warto stworzyć metody z mniejszą liczbą argumentów, które później wywołują docelową metodę, przekazując właśnie Null Object.

Fajnym ułatwieniem, jakie pokazał Tomek, było Simplify. Przykładowo poniższy kod:



Zostanie przekształcony do:
System.out.println("bbb");
Ważne by kursor przed naciśnięciem Alt + Enter, w jedynym słusznym środowisku, znajdował się na if'ie.

Kolejnym przykładem był refkatoring kodu wywołujący pewne zadanie (Task) w wątku i bloku synchronizowanym lub nie. Prelegent ładnie przerefaktorował kod pełen if'ów na synchronizowany dekorator Taska, kod Taska i klasę tworzącą odpowiednie wątki. czyli dostaliśmy ładne rozdzielenie odpowiedzialności.

Później było o tym gdzie używać if'ów. Było na temat tego pytania: Get rid of ugly if statements i sensowności różnych implementacji. Polecam przejrzeć wątek, zwłaszcza odnośnie wielomianu 45 stopnia.

Kolejnym przykładem było porównywanie dat i jeśli daty się nie zmieniają w wymaganych, to czasem dla czytelności lepiej je zahardkodować. Jeśli musimy te daty wczytać skądś, to lepiej skorzystać z Chain of responsibility.

Na koniec było jeszcze trochę na temat wizytatora (odwiedzającego). Wzorzec ten pomaga nam się pozbyć instanceof i rzutowania w dół. Gdy przyjdzie nam dodać nowy typ to jednak trzeba wszystkie dotychczasowe klasy o jedną metodę rozszerzyć.

Co do samej organizacji, to lampa w środkowym rzutniku była mocno wypalona, przez co obraz był nieostry. Na szczęście były jeszcze dwa po bokach i po ściemnieniu światła można było wygodnie się gapić, na to prelegent robił.

Co do samej prezentacji, to bardzo mi się podobała - dużo wiedzy, szybkie tępo, brak nudy, praktyczna wiedza.

Następnie było wystąpienie Pawła Cesara Sanjuana Szklarza na temat złożonych architektur bez podziału na warstwy. Prelegent przedstawiał swoje rozwiązanie dotyczące wstrzykiwania zdalnych wywołań w ramach innych maszyn wirtualnych Javy. Ma to na celu unikniecie kudu, który tylko deleguje zdania dalej. Nie będę się tutaj więcej rozpisywał, kod do przejrzenia jest tutaj: github.com/paweld2/Service-Architecture-Model.

Na koniec był jeszcze wykład "How to be awesome at a Java developer job interview" Wojciecha Seligi Opowiadał on, jakie cechy powinien mieć kandydat na stanowisko w jego firmie (lub ogółem w firmie która chce mieć tylko najlepszych w swoim fachu). Wykład sponsorowany, ale wzbudził wiele kontrowersji.

Pierwsza sprawa to języki (ale nie programowania). Powinniśmy znać język klienta, dla którego będziemy pracować, język firmy w której będziemy pracować i oczywiście angielski.

Później prelegent przedstawiał dwa typy kandydatów, których nie lubi. Pierwszy z nich to certyfikowani analfabeci, którzy mają górę certyfikatów, ale niewielką wiedzę. Brakuje im znajomości pakietu java.util.concurrent, Garbage Collectora, zarządzania zasobami w JVM, programowania sieciowego, wielowątkowego i stosu IP.

Swoją opinię na ten temat przedstawił już Koziołek we wpisie Rozbrajamy minę.... Ja od siebie dorzucę, że rzeczywiście wg. mnie nie każdy musi znać java.util.concurrent. Znajomość wątków i podstawowych metod synchronizacji jest jednak konieczna, a nieznajomość nowego kodowania wielowątkowego zazwyczaj wynika z używania starej wersji Javy w projekcie, jak i przerzucenia sporej odpowiedzialności na serwer aplikacji, w przypadku typowego programowania webowego. I w typowych obecnych projektach rzadko się wątki wykorzystuje. A w pracy są przecież potrzebni przedewszystkim typowi rzemieślnicy, którzy wyrzeźbią wymagania funkcjonalne klienta, które bardzo często kończą się na wczytaj, pokarz, zmodyfikuj, sprawdź, zapisz…

Analogicznie ma się sprawa do znajomości GC i zarządzania zasobami. Jak już w projekcie się zdarzy, że coś tam trzeba pogrzebać, to robi to część zespołu i nie każdy ma okazję wtedy poeksperymentować. Nawet jak już się będzie tym szczęśliwcem, to po kilku miesiącach już się nic nie pamięta. Co do stosu IP to pamiętam, że model OSI ma 7 warstw a TCP/IP 4, które jakoś tam odpowiadają jedne drugim. Ale na rozmowie kwalifikacyjnej na stanowisko kodera javy nie przypomniałbym sobie za nic nazw tych warstw i nawet nie przyszło by mi do głowy, aby sobie o tym przypominać.

Z drugiej strony powinniśmy równać w górę a nie w dół, więc trzeba będzie kiedyś wrócić do tych tematów i je ogarnąć.

Kolejny typ kandydata do pracy, którego Wojciech nie lubi to Astronauci którzy mają o sobie nie wiadomo jakie mniemanie.

Dalej było o umiejętnościach typowo technicznych, jakie powinien posiadać dobry kandydat. Nie będę ich tutaj wymieniał a jedynie odeślę do prezentacji. Jak kolejnym razem przyjdzie mi się zmierzyć z jakimś rekruterem, to na pewno przypomnę sobie tą prezentację przed rozmową. Warto też przeczytać komentarz autora pod prezentacją, czyli odpowiedź na stawiane zarzuty. Rzeczywiście prezentacja wywołała sporo emocji - bardzo mnie to cieszy, cel prelegenta osiągnięty - brawo!

Prelegent polecał jeszcze książkę Java Concurency in practice. A z dalszą częścią prezentacji już ciężko się nie zgodzić. Feedback po rozmowie powinien być telefoniczny (choć e-mailowy też mi się podoba), na rozmowie powinno być prawdziwe kodowanie, a rekrutacje powinni przeprowadzać najlepsze osoby z firmy, choćby się paliło i waliło w projekcie. Ponadto najlepsi chcą pracować tylko z innymi najlepszymi, lub jeszcze lepszymi.

Było jeszcze o trudnych pytaniach od kandydatów, czyli o ścieżkę rozwoju, możliwości awansu i gwarancję stabilności. Pytania te nauczyły nas stawiać korporacje, które na dzień dobry mydlą oczy takim lukrem (ścieżki kariery, awansu, rozwoju, szkolenia, wyjazdy i inne benefity).

Co do różnicy zdań jeszcze, to Wojtek szuka profesjonalistów, ale sam nie jest bez skazy. Już podczas pisania relacji z tegorocznej konferencji 33 degree, zwracałem uwagę na usuwanie testów, jeśli są przez dłuższy czas czerwone. Raczej nie po to ktoś się wysila aby napisać test, aby go potem usunąć, gdy nie działa. A druga sprawa (o której wtedy zapomniałem napisać) to niby 10 lat doświadczenia w tworzeniu testow, a na prezentacji (tej z 33 degree) był kod testu, gdzie na zmianę były wywoływane testowane metody, assercje, metody, assercje itd. I nazywane było to testem jednostkowym...

Na sam koniec było trochę reklamy, ale skoro to wykład sponsorowany to nich będzie. Mimo że się w kilku punktach nie zgadam z prezenterem, to prezentację uważam za bardzo udaną i wartościową, gdyż bylem ciekaw spojrzenia z punktu widzenia osoby rekrutującej.

Po ostatnim wykładzie było losowanie nagród, podziękowania itd.

Wieczorem jeszcze udałem się na imprezę integracyjną. Była wynajęta kręgielnia wraz ze sponsorowanymi grami piwem i pizzą. Czyli nikt nam nie przeszkadzał i można było się spokojnie zrelaksować.

Wielkie brawa dla organizatorów, wolontariuszy i innych zaangażowanych w konferencję. Dobra robota. Co do minusów to już Koziołek pisał o braku foyer, przez co z powodu natłoku ludzi ciężko było się przemieszczać w trakcie dziesięciominutowych przerw. Ale i tak fajnie że mój postulat z poprzedniego roku na temat wydłużenia przerw został spełniony, więc więcej nie narzekam. Brakowało mi jeszcze oznakowań (albo ich nie widziałem) / planu sal, klimy. Lekkie przepełnienie uczestników utrudniało przemieszczanie się pomiędzy wykładami, a w powidle było za dużo światła (brak zasłoniętych żaluzji). Widać było, że organizatorzy się bardzo starali, ciągle biegali i mieli kupę roboty. Jeszcze trochę i nie będzie czego poprawiać.

Podobne wypisy można znaleźć na stronie konferencji.

czwartek, 28 czerwca 2012

Mój pierwszy publiczny screencast z programowania w J2ME


Jakiś czas temu wspominałem, że opublikuje filmik z tworzenia implementacji gry tank2012 (klonu tank1990). I o to jest (oglądać w HD):


Post produkcja zajęła mi więcej niż przypuszczałem. Musiałem trochę powycinać, gdy gdzieś pod koniec nagrywania musiałem zaglądać do ściągawki. Jednak jak popełniałem jakieś błędy w kodzie, to je naprawiałem. Teraz każdy może sobie popatrzeć jak programuję. Nie jest to może najefektywniejsze wykorzystanie możliwości jakie daje IDE, ale już dawno nie siedziałem na NetBeansie.

Opublikowałem również pełen kod na githubie. Jest on nie bardzo obiektowy, ale nie to było celem ćwiczenia, a pokazanie jak można tą grę zaimplementować. Co prawda w oryginalnej wersji cegły można było niszczyć stopniowo, ale w przypadku j2me wymagało by to sporo wysiłku. No i zapomniałem po każdym zadaniu wykonać commita, więc mamy rewizję początkową i końcową.

Wszelkie uwagi odnośnie kodu / screencast'a mile widziane. A teraz czas się zwijać na Confiturę.