3. Prolog
Niniejsza prezentacja zawiera materiał, który chciałem
zaprezentowad podczas konferencji Confitura 2012
(http://confitura.pl), ale nie wszystko się udało, gdyż:
• mój organizm utracił min. 50% swoich zdolności
psycho-fizycznych na skutek zbyt wysokiej temperatury,
pot zalewał oczy i nawet pisanie na klawiaturze
sprawiało kłopoty
• nie wszystkim było dane zobaczyd co dzieje się na
ekranie (tj. rzędom od 4-go w górę na pewno nie)
… a przynajmniej tak to sobie tłumaczę
3
4. Agenda
1. Motywacja – dlaczego taki temat ?
2. Trochę teorii o InvokeDynamic
3. Praktyka
– zaczynając od „Hello World”
4
7. Motywacja negatywna
• bardzo mało informacji o InvokeDynamic
– szczególnie w polskim Internecie
• InvokeDynamic w google (PL): ~20 wyników za ostatni rok ?!?
• fatalny marketing Oracle, wpychający InvokeDynamic w niszę:
„tylko dla ludzi implementujących języki dynamiczne na JVM”
– to jak powiedzied, że refleksja w Javie jest użyteczna tylko dla
wybraoców
– pewnie konsekwencją tego jest mała liczba i słabe tutoriale (na
pewno nie są to tutoriale typu „Hello World”), np.:
http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html
• wrodzona przekora i syndrom „bo ja to widzę inaczej”
7
8. Dlaczego warto poznad InDy ?
• nowy, fundamentalny składnik JVM
– nowy format klas (constant pool, .class)
– nowy bytecode (po raz pierwszy w historii technologii Java !)
– nowe API w podstawowym pakiecie java.lang
• Java 8: łatwiej będzie zrozumied implementację lambda
expressions (czyli upraszczając: „closures w Java”)
– oba projekty, tj. invokedynamic i lambda expressions mają ze sobą wiele
wspólnego
• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
• wpływ na inne-niż-Java języki programowania dla JVM
– OK, trudno, ale muszę wspomnied o wykorzystaniu invokedynamic przez
języki dynamiczne na JVM (np. Groovie, Scala, JRuby, Jython, JavaScript)
• ale moja prezentacja w ogóle nie porusza tematu języków dynamicznych dla JVM ! Będę
mówid wyłącznie o wykorzystaniu InDy w języku Java. 8
9. Po co InvokeDynamic ?
Żeby łatwiej tworzyd dynamiczny, generyczny kod,
w dodatku wydajnie wykonywany przez JVM.
• z tego samego powodu mamy w Java m.in. refleksję,
adnotacje, dynamiczne ładowanie kodu czy generyki
9
10. Po co InvokeDynamic ?
• … ale: wszystko co daje InDy można dzisiaj zrobid
innymi metodami („zasymulowad”)
• prawda, tyle tylko, że dzięki InDy:
– będzie prościej, bo… nie musimy tego robid
• „Najlepszy kod to taki którego nie trzeba pisad”
– będzie wydajniej, bo:
• kod z InDy jest lepiej „rozumiany” przez kompilator JIT JVM
=> JIT będzie w stanie wykonad o wiele więcej optymalizacji,
niż przy zastosowaniu „symulatorów”
10
11. Ważna uwaga
• większośd przykładów i kodu, który tutaj pokazuję ukradłem
– adaptacja filozofii: „najlepszy kod, to taki, którego nie trzeba pisad”
• mój mały wkład tutaj polega na tym, aby:
1. „odczarowad” temat InvokeDynamic
2. poprzez przykłady - czasem trywialne - zachęcid do samodzielnego
poznawania InDy
3. ułatwid czytanie ze zrozumieniem kodu wykorzystującego InDy,
dostępnego w sieci
• i czerpad z tego taką przyjemnośd, jaką ja miałem (i mam nadal)
• chodby kodu publikowanego tutaj:
– http://code.google.com/p/jsr292-cookbook/
– http://www.java.net/blogs/forax/
– https://blogs.oracle.com/jrose/
– http://blog.headius.com/ 11
14. MethodHandle
wskaźnik / uchwyt / referencja do metody
• „metoda” oznacza tutaj także operację dostępu
do pól (obiektów i klas), konstruktorów, super,
itd.
– a nawet dostęp do… metod, które nie istnieją .
14
15. MethodHandle - Hello World
• do zabawy z większością tego co oferuje Indy
wystarczy Java 7 i IDE
• czyli tyle wystarczy rozumied, aby zacząd :
public class HelloIndyWorld {
public static void main(String[] args) {
}
}
15
16. MethodHandle – Hello World
Do tworzenia uchwytów do metod używamy
m.in. metod (statycznych) klasy
package pl.confitura.invokedynamic; MethodHandles.
Śmiesznie, ale w tym przykładzie tworzymy
uchwyt do nieistniejącej metody !
import java.lang.invoke.MethodHandle; constant zawsze zwraca stałą wartośd, tu:
import java.lang.invoke.MethodHandles; typu String, równą „Hello World !”.
Metoda invoke wywołuje metodę na którą
wskazuje uchwyt.
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");
System.out.println(mh.invoke());
}
}
16
18. MethodHandle
• jest bardzo lekką konstrukcją
– (sorry za powtarzanie się) zrozumiałą dla JIT (a także GC) w JVM
• niemal natychmiastowe wykorzystanie MethodHandle to zastąpienie
nimi refleksji
– refleksja (java.lang.reflect), mimo iż od Java 1.4 poważnie
udoskonalona, to jest znacznie wolniejsza od MethodHandle.
Powody są dwa:
1. przy każdym wywołaniu metody z użyciem refleksji *poprzez
invoke() z java.lang.reflect.Method+, następuje sprawdzenie
praw dostępu kodu wołającego do tej metody. W
MethodHandle, to sprawdzenie następuje tylko przy pobieraniu
uchwytu do metody *np. za pomocą lookup()+.
2. w refleksji konieczny jest boxing i inne konwersje. Z kolei,
uchwyt do metody jest silnie-typizowany, w tym możliwe jest
posługiwanie się prymitywami bez ich konwersji do typów
18
referencyjnych.
19. MethodHandle - kombinatory
• API udostępnione w ramach InvokeDynamic pozwala na
całkiem złożone „manipulacje” uchwytami do metod
– możemy dostawad się do różnego rodzaju metod (statycznych,
wirtualnych, konstruktorów, itp.), a także w szerokim zakresie
manipulowad ich typami, parametrami, zwracanymi wynikami, itp.
– jest nawet konstrukcja przypominająca if-then-else
– zasadniczo to API MethodHandle jest „Turing complete”
• zwykle wynikiem tych manipulacji są nowe uchwyty do metod
• możliwe jest „składanie” manipulacji (jak funkcji: f ( g ( h (x) ) )
• ciąg (łaocuch) manipulacji na uchwytach tworzy graf
• ten graf (=intencja programisty) jest zrozumiała dla
kompilatora JIT maszyny wirtualnej Java
– To jest kluczowe źródło wydajności InvokeDynamic, bo mimo potencjalnie dużej
złożoności całego grafu manipulacji), JIT wciąż (w dużym stopniu) jest w stanie aplikowad
swoje optymalizacje np. method inlining
19
– JIT może taki graf „przejśd”, węzeł po węźle i „zrozumied” go
20. MethodType – typ metody
• związany z MethodHandle
• określa dokładny typ metod i uchwytów do metod
– czyli: typy parametrów metody i zwracanego przez nią wyniku
• mimo dodania InvokeDynamic, JVM nie przestaje byd silnie
typizowalna (strong typing)
– a zatem te z optymalizacji JIT, które korzystają z informacji o typach,
mogą byd wciąż aplikowane
• methodType posiada metody pozwalające na tworzenie
odpowiednich typów oraz manipulacje na nich
• wskazówka: mimo, iż to mało atrakcyjne zagadnienie, to warto je
dobrze poznad, bo wiele błędów w zabawach z InDy bierze się z
niezgodności typów, a:
1. w zdecydowanej większości dają one o sobie znad dopiero w czasie
wykonania (kompilator daje tu niewiele)
2. JVM jest absolutnie bezwzględny jeśli chodzi o zgodnośd typów !
• bezpieczeostwo i szybkośd
20
21. Nowy bytecode: INVOKEDYNAMIC
Pierwsze w historii rozszerzenie listy instrukcji JVM !
• chod można spekulowad, że o czymś takim myślano już
w momencie powstawania technologii Java, bo:
– kod tej instrukcji był od początku zarezerwowany (BA)
– nieprzypadkowo (?) jest ulokowany razem z pozostałymi
instrukcjami wywołania metod (INVOKESTATIC,
INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE)
– tak naprawdę, to historycznie HotSpot VM powstał nie dla
języka Java, a dla języków… Smalltalk oraz Scheme , w
których jest znacznie większa niż w języku Java możliwośd
wpływu programisty na sposób wywoływania metod
21
22. Bytecode: INVOKEDYNAMIC
• przy pierwszym napotkaniu tej instrukcji, JVM wywołuje metodę (tzw.
BSM – bootstrap method), której zadaniem jest określid docelową
metodę (uchwyt) która będzie wywołana
• To programista tworzy bootstrap method. W sumie to jest zwykła
metoda…
• BSM (tj. jej nazwa, klasa w której ona się znajduje, jej typ - parametry,
wynik - oraz opcjonalnie dodatkowe parametry) są obowiązkowymi
argumentami instrukcji INVOKEDYNAMIC
• BSM JEST ZAWSZE WYKONYWANA TYLKO 1 RAZ !
– i TYLKO przy PIERWSZYM napotkaniu danego wywołania InvokeDynamic !
• w argumentach INVOKEDYNAMIC, po tych które dotyczą BSM,
następują pozostałe argumenty (np. nazwa wywoływanej metody i jej
argumenty)
22
23. CallSite
• wynikiem wykonania się BSM jest utworzenie
obiektu klasy java.lang.invoke.CallSite
• wewnątrz tego obiektu jest docelowa metoda
(uchwyt), którą wykona instrukcja
INVOKEDYNAMIC, po powrocie z BSM
• „CallSite” czyli: „miejsce wywołania metody”
– czyli de facto dostajemy referencję do „miejsca” w
którym umieszczona jest instrukcja INVOKEDYNAMIC
• czyli potencjalnie możemy zmodyfikowad to „miejsce”, tj.
podstawid tam inny uchwyt
– dynamizm InDy w akcji !
23
24. CallSite
• API udostępnia 3 specjalizowane klasy CallSite:
– ConstantCallSite – czyli informujemy JVM, że po wykonaniu
BSM, docelowa metoda (target), nie ulegnie zmianie
• czyli informujemy JIT, że może bez obawy aplikowad te ze swoich
optymalizacji, które zakładają, że zawsze będzie wywoływana ta sama
metoda (czyli należy oczekiwad, że szybkośd takiego wywołania
będzie porównywalna do wywołania „zwykłego”, np. statycznego)
– MutableCallSite (oraz jej wariant VolatileCallSite), które
informują JIT, że docelowa metoda (target) może ulec
zmianie
• można tworzyd swoje własne specjalizacje klas CallSite !
– z własną logiką, stanem, itp.
24
25. Inne przydatne konstrukcje
API InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcji
pomocniczych (prostszych i/lub bardziej wydajnych niż gdyby samodzielnie
budowad ich odpowiedniki):
• java.lang.ClassValue<T> – pozwala programiście przypisad do obiektów Class
swoje własne wartości. Najczęściej używane jako bufor, podobny do
ThreadLocal, z tym, że zamiast z wątkiem, wartości są „związane” z klasą
– będzie później przykład pokazujący do czego i jak taki bufor można użyd
• SwitchPoint – rodzaj semafora, który bezpiecznie wątkowo może
poinformowad o pewnej zmianie związane z nim uchwyty
– będzie później przykład praktyczny, który to lepiej objaśni
• MethodHandleProxies – pozwala utworzyd obiekt implementujący określony
interfejs z 1 metodą (czyli tzw. SAM – Single Abstract Method);
„implementacją” ten metody będzie podany uchwyt
– też będzie przykład (i to krótki, acz super-fajny)
25
28. Co potrzeba ?
• Java 7
– im wyższa wersja, tym lepszej wydajności
InvokeDynamic należy się spodziewad
• IDE
– tworzymy zwykły projekt Java
28
29. MethodHandle – Hello World (było)
Do tworzenia uchwytów do metod używamy
m.in. metod (statycznych) klasy MethodHandles.
package pl.confitura.invokedynamic;
Śmiesznie, ale w tym przykładzie tworzymy
uchwyt do nieistniejącej metody !
Constant zawsze zwraca stałą wartośd, tu: typu
import java.lang.invoke.MethodHandle; String, równą „Hello World !”.
import java.lang.invoke.MethodHandles; Metoda invoke wywołuje metodę na którą
wskazuje uchwyt.
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.constant(String.class, "Hello World !");
System.out.println(mh.invoke());
}
}
29
30. MethodHandle – identity
Uchwyt do metody uzyskany poprzez
identity, gdy jest wywołany, zwraca swój
argument (podanego typu, tu: String)
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.identity(String.class);
System.out.println(mh.invoke("Hello InDy World !"));
}
} To jest argument do uchwytu do metody
(tu: mh). Te argumenty będą przekazane
do metody na którą wskazuje uchwyt (tu:
identity).
30
31. MethodHandle – type
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.identity(String.class);
System.out.println(mh.type());
}
Pozwala określid typ metody, czyli jakiego
} typu są parametry i wynik podanej
metody.
Więcej informacji: klasa MethodType w
java.lang.invoke
31
32. MethodHandle – invoke, invokeExact
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
MethodHandle mh = MethodHandles.identity(String.class);
//System.out.println(mh.invoke("Hello InDy World !"));
System.out.println(mh.invokeExact("Hello InDy World !"));
} Dostępnych jest kilka sposobów wywołania uchwytu do
metody:
} • invoke
• invokeExact
• invokeWithArguments
Invoke dokonuje opcjonalnie konwersji argumentów i
wyniku do tego co oczekuje wywołujący (caller).
InvokeExact jest szybszy, ale wymagana jest 100%
zgodnośd typów między wywołującym a metodą
(uchwytem). Tu: println oczekuje Object, a invokeExact
zwraca String. Konwersja NIE jest wykonywana. Stąd
błąd czasu wykonania (poniżej). Bo konieczny jest cast
do String.
32
33. MethodHandle – Polymorphic Signature
• InvokeDynamic wprowadza także ciekawostkę w postaci tzw. polimorficznych sygnatur
metod
• zauważ, że sygnatura metod invoke/invokeExact wynika z tego ile i jakie parametry do niej
są przekazywane !
• kompilator Java i weryfikator bytecode’u (a także narzędzia, tu: Eclipse) dopuszczają jako
poprawne takie wywołania. Niestety, adnotacja @PolymorphicSignature nie jest
dostępna dla naszego kodu … 33
34. MethodHandle – coś ciekawszego: uchwyt do własnej metody
public class HelloIndyWorld { Lookup pozwala uzyskad uchwyt do
public static class MyClass { różnorodnych metod, np. tutaj do metody
statycznej myConcat w klasie MyClass.
static public String myConcat(String s1, String s2) {
return s1 + s2; W 100% muszą zgadzad się nie tylko nazwa
} metody, ale także jej sygnatura (określona
przez typ metody – MethodType [mt])
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findStatic(MyClass.class, "myConcat", mt);
System.out.println((String) mh.invokeExact("Ala ", "ma kota"));
}
}
34
35. MethodHandle – uchwyt do metody wirtualnej
public class HelloIndyWorld {
public static class MyClass {
private String s;
Metody wirtualne klasy wyszukujemy za pomocą
public MyClass(String s) { findVirtual.
this.s = s;
W metodach wirtualnych, implicite, ich pierwszy
} argument określa obiekt na którym ta metoda
będzie wykonana (receiver).
public int howManyCharacters(String s) { Za pomocą bindTo możemy utworzyd uchwyt do
return (s + this.s).length(); metody, w którym ten argument (receiver) będzie
} na stałe ustawiony na wybrany obiekt.
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(int.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "howManyCharacters", mt);
MyClass obj = new MyClass("Ala");
MethodHandle mhBound = mh.bindTo(obj);
System.out.println((int) mhBound.invokeExact("ma kota"));
}
}
35
36. MethodHandle – uchwyt do metody wirtualnej (2)
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) {
this.s = s;
}
public int howManyCharacters(String s) {
return (s + this.s).length();
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(int.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "howManyCharacters", mt);
System.out.println((int) mh.invokeExact(new MyClass("Ala"), "ma kota"));
}
} Można też tak…
36
37. MethodHandle – składanie uchwytów (kombinatory)
public class HelloIndyWorld {
public static class MyClass {
private String s; Przykład utworzenia nowego uchwytu do
metody – za pomocą metody
public MyClass(String s) { filterArgument. Najpierw na podanym
this.s = s; argumencie (0, czyli „ma kota”), wykona się
} metoda wskazywana przez uchwyt
mhToUpper (czyli metoda wirtualna
public String myVirtualConcat(String s) { „toUpperCase” dla obiektu klasy String).
return this.s + s; Zwrócony z niej wynik (czyli „MA KOTA”)
} będzie nowym argumentem dla mh
} (pierwszy argument w filterArguments).
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtToUpper = MethodType.methodType(String.class);
MethodHandle mhToUpper = MethodHandles.lookup().findVirtual(String.class, "toUpperCase", mtToUpper);
MethodHandle mhCombined = MethodHandles.filterArguments(mh, 0, mhToUpper);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
37
38. MethodHandle – filtrowanie wyników metod
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) { Tu najpierw wykonywana jest metoda na
this.s = s; którą wskazuje mh (czyli myVirtualConcat),
} a następnie na jej wyniku (czyli „Ala ma
kota”) wykonywana jest metoda na którą
public String myVirtualConcat(String s) { wskazuje mhToUpper.
return this.s + s;
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtToUpper = MethodType.methodType(String.class);
MethodHandle mhToUpper = MethodHandles.lookup().findVirtual(String.class, "toUpperCase", mtToUpper);
MethodHandle mhCombined = MethodHandles.filterReturnValue(mh, mhToUpper);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
38
39. MethodHandle – interceptor typu ‚before’
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) { foldArguments działa w ten sposób, że najpierw
this.s = s; wykonywany jest drugi argument (mhInterceptor), a
} jego wynik (o ile nie jest void) jest WSTAWIANY
(INSERT) jako argument do wywołania pierwszego
public String myVirtualConcat(String s) { argumentu (mh).Po czym wywoływany jest mh.
return this.s + s; Czyli poniższy kod nie zadziała. Dlaczego ? Bo po
} wstawieniu dodatkowego argumentu (czyli wyniku z
mhInterceptor) nie zgadzają się typy uchwytów w
public static String myInterceptor(String s) { foldArguments (a muszą one byd takie same).
System.out.println("Intercepted, with arg s: " + s); Komunikat błędu jest przy tym dosyd mylący …
return "^" + s + "^";
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtInterceptor = MethodType.methodType(String.class, String.class);
MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);
MethodHandle mhCombined = MethodHandles.foldArguments(mh, mhInterceptor);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
39
40. MethodHandle – interceptor typu ‚before’ (poprawniej)
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) {
this.s = s;
}
public String myVirtualConcat(String s) { Za pomocą metody dropArguments trzeba pozbyd się
return this.s + s; niepotrzebnego już argumentu (czyli „ma kota”).
} Pozycja argumentów liczona jest od 0, ale ponieważ
wynik wywołania mhInterceptor jest WSTAWIANY na
public static String myInterceptor(String s) { pozycję 0, to oryginalne argumenty przesuwają się (czyli
System.out.println("Intercepted, with arg s: " + s); „ma kota” jest teraz na pozycji 1).
return "^" + s + "^";
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtInterceptor = MethodType.methodType(String.class, String.class);
MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);
MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mh, 1, String.class),
mhInterceptor);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
40
41. MethodHandle – interceptor typu ‚before’ (najpoprawniej )
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) {
this.s = s;
} Bo tak jest bardziej generycznie. Typ usuwanego
argumentu jest zgodny z typem parametru metody
public String myVirtualConcat(String s) { myVirtualConcat). Dla wyjaśnienia:
return this.s + s; • typ mh to: (String)String
} • mh.type().parameterType(0) odnosi się do
pierwszego (i tu jedynego) argumentu metody
public static String myInterceptor(String s) { myVirtualConcat, sprzed wstawienia wyniku z
System.out.println("Intercepted, with arg s: " + s); interceptora
return "^" + s + "^";
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtInterceptor = MethodType.methodType(String.class, String.class);
MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);
MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mh, 1,
mh.type().parameterType(0)), mhInterceptor);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
41
42. MethodHandle – interceptor typu ‚after’
public class HelloIndyWorld {
public static class MyClass {
private String s;
public MyClass(String s) {
this.s = s;
}
public String myVirtualConcat(String s) {
return this.s + s;
}
public static String myInterceptor(String s) {
System.out.println("Intercepted, with arg s: " + s);
return "^" + s + "^";
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findVirtual(MyClass.class, "myVirtualConcat", mt);
mh = mh.bindTo(new MyClass("Ala "));
MethodType mtInterceptor = MethodType.methodType(String.class, String.class);
MethodHandle mhInterceptor = MethodHandles.lookup().findStatic(MyClass.class, "myInterceptor", mtInterceptor);
MethodHandle mhCombined = MethodHandles.foldArguments(MethodHandles.dropArguments(mhInterceptor, 1,
mh.type().parameterType(0)), mh);
System.out.println((String) mhCombined.invokeExact("ma kota"));
}
}
42
43. MethodHandles / MethodHandle
Dostępne są także metody:
• wstawiające argumenty wybranego typu (insertArgument)
• zmieniające kolejnośd argumentów (permuteArguments)
• tworzące uchwyty przechwytujące wyjątki (catchException)
• tworzące uchwyty rzucające wyjątki (throwException)
• konwertujące argumenty do podanych typów
(MethodHandle.asType)
• dopasowujące uchwyt, tak aby argumenty były przekazywane w
postaci tablicy (MethodHandle.asCollector) lub odwrotnie
(MethodHandle.asSpreader)
• obsługujące uchwyty o zmiennej liczbie parametrów
(MethodHandle.asVarargsCollector)
• …
43
44. MethodHandles
• jest nawet dostępna konstrukcja IF-THEN-ELSE !
– MethodHandles.guardWithTest
• szczegóły (m.in. dotyczące typów argumentów) są
podane w dokumentacji API:
– http://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.html
44
45. MethodHandle – przykład z .guardWithTest
public class HelloIndyWorld {
public static class MyClass {
public static String withDog(String s) {
return s + " ma psa";
}
public static String withCat(String s) {
return s + " ma kota";
}
}
public static void main(String[] args) throws Throwable {
MethodType mtDog = MethodType.methodType(String.class, String.class);
MethodHandle mhDog = MethodHandles.lookup().findStatic(MyClass.class, "withDog", mtDog);
MethodType mtCat = MethodType.methodType(String.class, String.class);
MethodHandle mhCat = MethodHandles.lookup().findStatic(MyClass.class, "withCat", mtCat);
MethodHandle mhTest = MethodHandles.identity(boolean.class);
mhTest = MethodHandles.dropArguments(mhTest, 1, mhDog.type().parameterType(0));
mhDog = MethodHandles.dropArguments(mhDog, 0, boolean.class);
mhCat = MethodHandles.dropArguments(mhCat, 0, boolean.class);
MethodHandle mhCombined = MethodHandles.guardWithTest(mhTest, mhDog, mhCat);
System.out.println((String) mhCombined.invokeExact(true, "Ala"));
System.out.println((String) mhCombined.invokeExact(false, "Ala"));
}
}
45
46. MethodHandle i refleksja (java.lang.reflect)
public class HelloIndyWorld {
public static void main(String[] args) throws Throwable {
java.lang.reflect.Method m = String.class.getDeclaredMethod("toUpperCase");
MethodHandle mh = MethodHandles.lookup().unreflect(m);
System.out.println((String) mh.invokeExact("Ala ma kota"));
}
}
46
47. MethodHandleProxies
• możliwe jest tworzenie obiektów, implementujących
podany interfejs (typu SAM, czyli z jedną metodą, tzw.
interfejs funkcyjny)
• implementacją tej metody będzie metoda wskazywana
przez podany uchwyt
47
48. MethodHandleProxies - przykład
public class HelloIndyWorld {
public interface MyInterface {
String myMethod(String s1, String s2);
}
public static class MyClass {
public static String myConcat(String s1, String s2) {
return s1 + s2;
}
}
public static void main(String[] args) throws Throwable {
MethodType mt = MethodType.methodType(String.class, String.class, String.class);
MethodHandle mh = MethodHandles.lookup().findStatic(MyClass.class, "myConcat", mt);
MyInterface myObj = MethodHandleProxies.asInterfaceInstance(MyInterface.class, mh);
System.out.println( myObj.myMethod("Ala ", "ma kota") );
}
}
48
50. Mały problem
• niestety, ale w Java 7, w języku Java nie ma składni pozwalającej na
bezpośrednie tworzenie dynamicznych wywołao metod
• na początku powstawania specyfikacji InvokeDynamic taka składnia
była dostępna:
String result2 = java.dyn.Dynamic.<String>getName(file);
• podjęto (IMHO słuszną) decyzję, aby składniowo zarówno
InvokeDynamic, jak i lambda expressions były do siebie jak
najbardziej zbliżone
– niestety: lambda expressions wypadła z Java 7 i ma się pojawid w Java 8
• niestety: wydanie Java 8 przesunęło się z początkowego „late-2012” na „late-2013”
(albo i jeszcze później). Niestety .
• trzeba poradzid sobie z problemem poprzez samodzielne
generowanie bytecode’u (zawierającego instrukcję INVOKEDYNAMIC)
– brzmi hardcore’owo, ale narzędzia takie jak ASM
(http://asm.ow2.org) znacznie to ułatwiają 50
51. Generowanie instrukcji INVOKEDYNAMIC
• W sieci jest kilka przykładów jak generowad instrukcję
INVOKEDYNAMIC
– http://weblogs.java.net/blog/forax/archive/2011/01/07/calling-invokedynamic-java
– http://nerds-central.blogspot.com/2011/05/performing-dynamicinvoke-from-java-step.html
• W moich przykładach (kod „edukacyjny”) zależało mi, aby było to
jak najprostsze (czyli np. bez potrzeby patchowania .class, jak w
większości przykładów z JSR-292 Cookbook)
– http://code.google.com/p/jsr292-cookbook
51
52. Wymagania
• Tak jak poprzednio, czyli:
– Java 7
– IDE
• Dodatkowo:
– ASM (http://forge.ow2.org/projects/asm)
• asm-all-4.0.jar
• najnowsze wersje są OK (tu używam ASM 4.0)
52
53. Github
• Tutaj znajduje się kod źródłowy pokazywanych przykładów:
– https://github.com/waldekkot/Confitura2012_InvokeDynamic
• Sposób wywoływania instrukcji INVOKEDYNAMIC znajduje się w
w/w repozytorium w projekcie DemoIndySecond:
https://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndySecond
• Ale na kolejnych slajdach będę poszczególne kroki wyjaśniad…
53
55. DemoIndySecond (krok I) InvokeDynamic.prepare, przygotowuje („wstawia” do kodu Java) instrukcję
bytecode’u INVOKEDYNAMIC, dzięki której możliwe jest dynamiczne
wywoływanie metod i kontrola nad tym procesem.
public class HelloInDyWorld1 { Implementacja InvokeDynamic.prepare jest wyjaśniona na późniejszych slajdach.
public static String myConfituraMethod(String s) { return s + " 2012"; }
„run me” to odpowiednik nazwy wywoływanej metody (czyli tak jakby wywoład
X.runme). A przynajmniej tak jak my, jako wywołujący (caller), to „widzimy”.
public static CallSite myBSM(MethodHandles.Lookup Warto zauważyd, że nie obowiązują nas ograniczenia co do identyfikatorów (tu
caller, String methodName, MethodType methodType, Object... bsmArgs)
{ jest spacja w nazwie wywoływanej metody ).
MethodHandle mh = null;
Drugi argument, to typ wywoływanej metody (znowu: z punktu widzenia
try { wywołującego).
mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",
Kolejne argumenty dotyczą BSM (bootstrap method), tj. jej nazwy, klasy w której
MethodType.methodType(String.class, String.class));
ona się znajduje, jej typu oraz jej (opcjonalnych) parametrów. Metoda BSM:
} catch (NoSuchMethodException | IllegalAccessException wykonywana co najwyżej jeden raz
• jest zawsze
e) { e.printStackTrace(); }
• i tylko przy pierwszym napotkaniu przez JVM danej instrukcji
return new ConstantCallSite(mh); INVOKEDYNAMIC (czyli very lazy, fakt, który jeszcze wykorzystamy…)
}
public static void main(String args[]) throws Throwable {
MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),
"myBSM", HelloInDyWorld1.class,
MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));
System.out.println( mh.invoke("Confitura") );
}
}
55
56. DemoIndySecond(krok II)
public class HelloInDyWorld1 {
public static String myConfituraMethod(String s) { return s + " 2012"; }
public static CallSite myBSM(MethodHandles.Lookup caller,String methodName,MethodType methodType, Object...bsmArgs)
{
MethodHandle mh = null;
try {
mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",
MethodType.methodType(String.class, String.class));
} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }
return new ConstantCallSite(mh);
} Bootstrap method.
publicJej zadaniemmain(String args[]) throws Throwable {
static void jest skonstruowad dane miejsce wywoływania metody, czyli określid za pomocą uchwytu do metody (MethodHandle)
docelową metodę (target), która będzie wywoływana kiedy JVM napotka daną instrukcję INVOKEDYNAMIC.
MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class),
Powtarzam się, ale: BSM jest wywoływana co najwyżej raz, przy pierwszym napotkaniu tę instrukcji INVOKEDYNAMIC.
Po wykonaniu się "myBSM", HelloInDyWorld1.class, obecne i każde następne wykonanie tej instrukcji INVOKEDYNAMIC
BSM (tj. ustaleniu docelowej metody),
spowoduje wykonanie ustalonej przez BSM docelowej metody.Lookup.class, String.class, MethodType.class, Object[].class));
MethodType.methodType(CallSite.class,
BSM zwraca obiekt opakowujący uchwyt do docelowej metody, czyli CallSite.
System.out.println( mh.invoke("Confitura") );
Możemy też przekazad naszą intencję, czy chcemy w przyszłości zmieniad docelową metodę (w tym przykładzie zwracamy
} ConstantCallSite, czyli informujemy JIT, że to miejsce wywołania będzie zawsze już odnosiło się do metody ustalonej w BSM).
} Referencję do obiektu CallSite można przechowywad w swoim kodzie. Można także tworzyd własne klasy specjalizowane CallSite.
Typ uchwytu do metody zwracanego przez BSM musi byd w 100% zgodny z tym, co określił wywołujący (caller) – tu: w drugim
argumencie prepare.
Do BSM, z miejsca wywołania metody, caller może przekazad dodatkowe parametry (bsmArgs). W sumie to także nazwa
wywoływanej metody („run me”) jest takim dodatkowym parametrem, bo jedyne co musi byd zachowane, to typ uchwytu
zwracany z BSM - musi byd zgodny z tym, co określił caller w callsite. 56
57. DemoIndySecond(krok III)
public class HelloInDyWorld1 {
public static String myConfituraMethod(String s) { return s + " 2012"; }
public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs)
{
MethodHandle mh = null;
try {
mh = MethodHandles.lookup().findStatic(HelloInDyWorld1.class, "myConfituraMethod",
MethodType.methodType(String.class, String.class));
Docelowa metoda (target).
} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }
Jej sygnatura (=typ, MethodType) NIE musi byd w 100% zgodna z tym, co
return new ConstantCallSite(mh); określił caller w miejscu wywołania (callsite). Za pomocą ciągu
} kombinatorów na uchwycie do tej metody, możliwe jest odpowiednie
dopasowanie tej metody do wymagao (typu) określonego w miejscu
wywoływania metody.
public static void main(String args[]) throws Throwable {
MethodHandle mh = InvokeDynamic.prepare("runTo pewnie oczywiste, ale to co przychodzi jako argument tej metody (tu:
me", MethodType.methodType(String.class, String.class),
"myBSM", HelloInDyWorld1.class, String s), to są argumenty przekazane w wywołaniu metody (tu:
„Confitura”). No chyba że, ciąg kombinatorów to zmodyfikował (, będzie
MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class));
później przykład pięknie to pokazujący).
System.out.println( mh.invoke("Confitura") );
}
}
57
59. InvokeDynamic.prepare
• własny kod pomocniczy (z braku składni w Java dla InvokeDynamic)
• pozwala utworzyd dynamiczne wywołanie metody z poziomu
zwykłego kodu Java
– korzysta z generatora bytecode’u (ASM)
– generuje nową klasę, a w niej metodę zawierającą instrukcję
INVOKEDYNAMIC - dynamicznego wywoływania metody
– ta instrukcja jest „skonfigurowana”
• poprzez parametry przekazane do InvokeDynamic.prepare
– nazwa i typ wywoływanej metody (=callsite)
– metoda bootstrap (BSM)
• w InvokeDynamic.prepare jest także trochę inna wersja – prepareAs
– która zamiast MethodHandle zwraca obiekt implementujący
podany interfejs funkcyjny (z 1-ną metodą). Wywołanie tej metody,
wywołuje de facto metodę zawierającą instrukcję INVOKEDYNAMIC
59
60. Klasa InvokeDynamic
• jej zrozumienie nie jest szczególnie trudne, ale trzeba mied
elementarną wiedzę o ASM
– lub rozumied wzorzec Visitor
• metody prywatne tej klasy po kolei tworzą strukturę nowej
klasy: klasy „z jedną metodą, a w tej metodzie jest instrukcja
INVOKEDYNAMIC”
• dostajemy wygenerowany ciąg bajtów (classfile) i za pomocą
swojego classloader’a ładujemy tę klasę i udostępniamy ją dla
naszego kodu Java, gdzie możemy woład jej metodę
60
61. HelloIndyWorld2
Przykład pokazuje, że do BSM można (z miejsca
wywołania !) przekazywad parametry, a tym samym
skonfigurowad sposób wywoływania metod z tego
miejsca
– dynamizm w akcji !
61
63. HelloIndyWorld3
• przykład pokazuje jak uczynid kod korzystający
z dynamicznego wywoływania metod bardziej
„normalnym”
• zamiast uchwytów do metod, mamy obiekt na
którym wywołujemy („normalnie” ) metodę
interfejsu, który ten obiekt implementuje
– ten interfejs, to tzw. functional interface
• http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
• w sumie to nie jedyny związek pomiędzy projektem „Java lambda
expressions” a invokedynamic…
63
65. DEMO III
czyli krótki benchmark wywołao metod:
InvokeDynamic vs. Static vs. Reflection
65
66. Po co taki (głupawy w sumie) benchmark ?
• bo dosyd często podnoszą się głosy, że taki
dynamiczny mechanizm wywoływania metod
musi byd wolny
– „jak pokazuje doświadczenie z refleksją…”
• niekoniecznie.
– po to przede wszystkim stworzono InvokeDynamic
(JSR-292), aby pogodzid dynamizm z wydajnością
• a przynajmniej dad JIT na to szansę
– z każdym kolejnym uaktualnieniem Java 7 spodziewałbym się
też coraz większej wydajności InDy
66
68. Benchmark
• 7-krotne wywołanie 100 mln razy poniższej metody:
public static long sumAndMultiply(long a, long b, int multiplier) {
return multiplier * (a + b);
}
• w sposób:
1. dynamiczny (przy użyciu InvokeDynamic)
2. statyczny
3. refleksyjny
4. refleksyjny bez autoboxing’u, czyli zamiast powyższej metody, wywoływana
jest:
public static Long sumAndMultiplyLong(Long a, Long b, Integer multiplier) {
return multiplier * (a + b);
68
}
70. Benchmark INVOKE DYNAMIC
1999800000000, TIME: 228 ms
1999800000000, TIME: 150 ms
1999800000000, TIME: 127 ms
1999800000000, TIME: 132 ms
1999800000000, TIME: 133 ms
1999800000000, TIME: 140 ms
1999800000000, TIME: 144 ms
Benchmark NORMAL (STATIC)
1999800000000, TIME: 142 ms
1999800000000, TIME: 154 ms
1999800000000, TIME: 122 ms
1999800000000, TIME: 135 ms
1999800000000, TIME: 139 ms
1999800000000, TIME: 131 ms
1999800000000, TIME: 126 ms
Benchmark REFLECTIVE
1999800000000, TIME: 4513 ms
1999800000000, TIME: 4258 ms
1999800000000, TIME: 4248 ms
1999800000000, TIME: 4290 ms
1999800000000, TIME: 4236 ms
1999800000000, TIME: 4156 ms
1999800000000, TIME: 4195 ms
Benchmark REFLECTIVE NO BOXING
1999800000000, TIME: 3077 ms
1999800000000, TIME: 2879 ms
1999800000000, TIME: 2921 ms
1999800000000, TIME: 3011 ms
1999800000000, TIME: 2910 ms
1999800000000, TIME: 3010 ms
1999800000000, TIME: 2845 ms 70
72. Ale po co ?
• pewnie wciąż zadajecie sobie pytanie, po co
ten cały InvokeDynamic ?
• odpowiedź wciąż brzmi:
– aby pisad bardziej dynamiczny kod
• w tym przykładzie będzie pokazane
wykorzystanie jednej z ważnych cech
InvokeDynamic (i mojej również)
– laziness
72
73. BSM
• jak pamiętacie, BSM dla instrukcji
INVOKEDYNAMIC jest wykonywana dopiero
wtedy, gdy JVM taką instrukcję napotka
• czyli można ten fakt wykorzystad do tego, aby
bardzo późno wykonywad pewne operacje (np.
inicjalizacje)
– „późno” = „dopiero gdy jest to potrzebne/używane”
73
74. Przykład – Lazy Constant
• załóżmy, że chcecie mied w swojej klasie stałą
wartośd (np. XML), ale inicjowaną poprzez
wykonanie jakiejś bardziej złożonej logiki
– załóżmy, że ta logika potencjalnie może
wykonywad się długo
• a w Waszej aplikacji czas jest istotny
74
75. Pomysł 1
Stała na poziomie klasy, inicjowana w bloku statycznym
public class ConstantResourceImpl implements ConstantResource {
public static final String xml;
static {
xml = ParseHelper.doSomeHeavyParsing("test.xml");
}
@Override
public void notNeedingTheXMLHere() {
//Hey, I am NOT using the 'xml' constant here !
}
@Override
public void badlyNeedingTheXMLHere() {
//I will be using the 'xml' constant here !
try {
System.out.println(xml);
} catch (Throwable e) { e.printStackTrace(); }
}
}
75
76. Zadziała ?
LazyConstantsMain.java
Zadziała, ale inicjalizacja tej stałej (czyli wykonanie tego mega-złożonego
parsowania) wykona się owszem raz, ale niezależnie od tego, czy tej stałej w
ogóle użyjemy czy też nie (czyli parsowanie wykona się zawsze).
public class LazyConstantsMain {
public static void main(String[] args) {
ConstantResource testObject = new ConstantResourceImpl();
testObject.notNeedingTheXMLHere();
}
}
76
77. Pomysł 2
(bardziej lazy, a w zasadzie to very lazy)
• „dostęp do wartości stałej” = „wykonanie metody zwracającej (stałą)
wartośd”
– patrz: MethodHandles.constant(T, v)
• niech inicjalizacja tej stałej odbywa się w BSM
– czyli wykona się co najwyżej 1 raz
– i tylko wtedy, gdy rzeczywiście jakiś kod odczytuje wartośd tej stałej
• ponieważ wszystko jest stałe (constant MethodHandle, ConstantCallSite),
to jest bardzo wysoka szansa, że JIT zaaplikuje wszystko co ma najlepsze
– optymalizacje typu inlining, constant folding, etc.
• czyli nic nie tracimy, a zyskujemy laziness
– a nawet pewien dodatkowy dynamizm – bo, to co (i/lub jak) jest
parsowane może byd określone dynamicznie (= w czasie wykonania)
– dodatkowo: podejście z InDy jest bardziej bezpieczne wątkowo ! Patrz
77
gwarancje określone w JVM spec.
78. public class ConstantResourceImplWithIndy implements ConstantResource {
private MethodHandle mh;
public static CallSite bootstrapForCallMe(MethodHandles.Lookup callerClass, String name,
java.lang.invoke.MethodType type, Object... args ) {
String xml = ParseHelper.doSomeHeavyParsing((String) args[0]);
return new ConstantCallSite( MethodHandles.constant( String.class, xml ) );
}
public ConstantResourceImplWithIndy(String resourceName) {
try {
mh = InvokeDynamic.prepare("callMe", MethodType.methodType(String.class, new Class<?>[] {}),
"bootstrapForCallMe", ConstantResourceImplWithIndy.class,
MethodType.methodType(CallSite.class, Lookup.class, String.class,
MethodType.class, Object[].class),
resourceName );
} catch (Throwable e) { e.printStackTrace(); }
}
@Override
public void notNeedingTheXMLHere() { //But, I am NOT using the 'xml' constant here ! }
@Override
public void badlyNeedingTheXMLHere() { //I will be using the 'xml' constant here !
try { System.out.println( mh.invoke()); } catch (Throwable e) { e.printStackTrace(); }
} 78
}
79. Zadziała ?
LazyConstantsMainWithIndy.java
Zadziała !
public class LazyConstantsMainWithIndy {
public static void main(String[] args) {
ConstantResource testObject = new ConstantResourceImplWithIndy( "test.xml" );
testObject.notNeedingTheXMLHere();
}
}
Dopóki nie użyjemy tej stałej, nie jest ona inicjalizowana i mega-złożone
parsowanie nie odbywa się.
79
80. A gdy użyjemy stałej…
public class LazyConstantsMainWithIndy {
public static void main(String[] args) {
ConstantResource testObject = new ConstantResourceImplWithIndy("test.xml");
testObject.badlyNeedingTheXMLHere();
}
}
80
81. Ten sam trick może byd użyteczny w
wielu Waszych programach…
81
83. DEMO V
czyli przyspiesz swój kod rekurencyjny
(a tak naprawdę, to: jak czerpad intelektualną przyjemnośd z
czytania kodu zawierającego InvokeDynamic)
(zwłaszcza, gdy tego kodu nie musisz napisad )
83
84. Czy masz pomysł jak przyspieszyd ten kod ?
Klasyka: obliczanie sumy N pierwszych liczb z ciągu Fibonacciego
public class FibonacciSum {
private static final long HOW_MANY_NUMBERS = 40;
public static long fib(long n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
System.out.println("SUM OF FIRST " + HOW_MANY_NUMBERS + " NUMBERS OF FIBONACCI SEQ …");
long start = System.currentTimeMillis();
long result = 0;
for (long i = 0; i < HOW_MANY_NUMBERS; i++)
result += fib(i);
System.out.println("TIME: " + (System.currentTimeMillis() - start) + " ms");
System.out.println("RESULT: " + new DecimalFormat("###,###,###,###,###").format(result));
}
}
84
85. Dlaczego ten kod działa wolno ?
• ten kod jest piękny – bardzo przejrzyście
wyraża intencję programisty
• ale (w większości języków) jest nieefektywny
– nie z powodu rekurencji…
– ale dlatego, że te same obliczenia są wykonywane
wielokrotnie
• i co gorsza zapominane…
85
86. Memoization
• wykorzystamy dynamiczne wywoływanie metod, aby ten
piękny i przejrzysty kod przyspieszyd
– nie naruszając jego piękna
• zastosujemy coś, co w literaturze IT nazywa się
‚memoization’, czyli zapamiętywanie wyników funkcji i
unikanie wykonywania ich powtórnie. W skrócie:
– zapamiętujemy wynik wykonania funkcji dla określonych
argumentów
– przed wykonaniem funkcji, sprawdzamy, czy funkcja z takimi
argumentami była już wykonywana
– jeśli tak, nie wykonujemy funkcji, tylko zwracamy zapamiętany
wynik
– jeśli nie, wykonujemy funkcję, a jej wynik (i argumenty)
zapamiętujemy
86
87. InvokeDynamic a memoization
InvokeDynamic dostarcza nam kilku ważnych elementów
• w dalszej części prezentacji po kolei je omówię oraz
wyjaśnię jak zostały one razem złożone dla osiągnięcia
przyspieszenia o które nam chodzi
• ten przykład to jest kod, który ukradłem z:
– http://code.google.com/p/jsr292-
cookbook/source/browse/trunk/memoize/src/jsr292/cookbook/memoize/
• mój drobny wkład polega na tym, że ten genialny kod
zrozumiałem i dzielę się moimi wrażeniami z innymi
87
88. Ale zanim nastąpią wyjaśnienia,
zobaczcie efekt koocowy !
SpeedRecurenceWithIndy.java
88
89. Zresztą, w zasadzie to nawet
zmieniliśmy złożonośd obliczeniową tej
metody!
[z wykładniczej na niemal stałą ;-)]
Dla N > 102 kooczy się pojemnośd long (263 -1) …
Zamieniając long na BigInteger można spokojnie
podad i N = 3000 (ponad 600 cyfrowa liczba). Czas:
89
~50 ms . Ten przykład też jest na GitHub-ie.
91. 1. Zamiast wywołao fib(n) użycie InvokeDynamic
public class SpeedRecurenceWithIndy {
private static MethodHandle mhDynamic = null;
public static long fib(long n) throws Throwable {
if (n == 0) return 0;
if (n == 1) return 1;
return (long) mhDynamic.invokeExact(n-1) + (long) mhDynamic.invokeExact(n-2); // return fib(n-1)+fib(n-2)
}
[...]
public static void main(String args[]) throws Throwable {
System.out.println("SUM OF FIRST " + HOW_MANY_NUMBERS + " FIBONACCI USING INVOKE DYNAMIC !");
MethodType bsmType = MethodType.methodType(CallSite.class, Lookup.class, String.class,
MethodType.class, Class.class);
mhDynamic = InvokeDynamic.prepare("fib", MethodType.methodType(long.class, long.class),
"myBSM", SpeedRecurenceWithIndy.class, bsmType,
SpeedRecurenceWithIndy.class);
long start = System.currentTimeMillis();
long result = 0L;
for (long i = 0; i < HOW_MANY_NUMBERS; i++)
result += (long) mhDynamic.invokeExact(i);
System.out.println("TIME: " + (System.currentTimeMillis() - start) + " ms");
System.out.println("RESULT: " + new DecimalFormat("###,###,###,###,###").format(result));
} 91
}
92. 2. Bufor do przechowywania wyników
private static ClassValue<HashMap<String, HashMap<Object, Object>>> cacheTables = new ClassValue<HashMap<String,
HashMap<Object, Object>>>() {
@Override
protected HashMap<String, HashMap<Object, Object>> computeValue(Class<?> type) {
return new HashMap<String, HashMap<Object, Object>>();
}
};
• oprócz uchwytów do metod i nowej instrukcji bytecode, InvokeDynamic dodaje także
mechanizm pozwalający na przypisanie wartości (obiektu) do dowolnej klasy
• http://docs.oracle.com/javase/7/docs/api/java/lang/ClassValue.html
• to trochę taki odpowiednik ThreadLocal, tyle, że zamiast z wątkiem to wartośd jest
związywana z klasą (Class<?>)
• w przykładzie, ClassValue pełni rolę bufora wyników wywołania metod
• tu sprawdzamy, czy dla danego argumentu jest dostępny wynik
• tu zapisujemy wynik wywołania metody
• trzypoziomowa struktura:
1. klasa (tu: SpeedRecurrenceWithIndy)
2. metoda (tu: fib)
3. argumenty metody (klucz) Notabene: szkoda, że dla klas anonimowych nie można
• wynik metody (wartośd) użyd nowego w Java 7 operatora diamond. Fajniej
byłoby napisad: „= new ClassValue<>()”, a kompilator
92
się domyślił reszty…
93. 3. Metody pomocnicze dla BSM (i ich uchwyty)
Rzeczy typu:
• NOT_NULL: sprawdzenie czy podany obiekt nie jest null
• MAP_GET, UPDATE: get i update (put) do HashMap’y (czyli bufora wyników)
public static boolean notNull(Object receiver) { return receiver != null; }
public static Object update(HashMap<Object, Object> cache, Object result, Object arg) {
cache.put(arg, result);
return result;
}
private static final MethodHandle NOT_NULL;
private static final MethodHandle MAP_GET;
private static final MethodHandle UPDATE;
static {
Lookup lookup = MethodHandles.lookup();
try {
NOT_NULL = lookup.findStatic(SpeedRecurenceWithIndy.class, "notNull",
MethodType.methodType(boolean.class, Object.class));
MAP_GET = lookup.findVirtual(HashMap.class, "get",
MethodType.methodType(Object.class, Object.class));
UPDATE = lookup.findStatic(SpeedRecurenceWithIndy.class, "update",
MethodType.methodType(Object.class, HashMap.class, Object.class, Object.class));
} catch (ReflectiveOperationException e) { throw (AssertionError) new AssertionError().initCause(e); } 93
}
94. 4. Bootstrap method (I)
Aaaaaaby zamienid statyczne wywołania * fib() + na dynamiczne, wystarczyłoby tyle:
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
ReflectiveOperationException {
MethodHandle mh = null;
try {
mh = MethodHandles.lookup().findStatic(SpeedRecurenceWithIndy.class, "fib", type);
} catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); }
return new ConstantCallSite(mh);
}
My chcemy jednak „wpiąd się” z bardziej złożoną logiką !
94
95. 4. Bootstrap method (II)
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
ReflectiveOperationException {
MethodHandle target = lookup.findStatic(staticType, name, type);
HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);
String selector = name + type.toMethodDescriptorString();
HashMap<Object, Object> cache = cacheTable.get(selector);
if (cache == null) {
cache = new HashMap<Object, Object>();
cacheTable.put(selector, cache);
}
MethodHandle identity = MethodHandles.identity(type.returnType());
WTF !?!
identity = identity.asType(identity.type().changeParameterType(0, Object.class));
identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
Spokojnie, będę po kolei całośd wyjaśniad !
MethodHandle update = UPDATE.bindTo(cache); Poznając InvokeDynamic warto poznad jeden trick :
update = update.asType(type.insertParameterTypes(0, type.returnType()));
TRZEBA CZYTAD OD KOŃCA (czyli od dołu w górę) !!!
MethodHandle fallback = MethodHandles.foldArguments(update, target);
fallback = MethodHandles.dropArguments(fallback, 0, Object.class);
MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);
MethodHandle cacheQuerier = MAP_GET.bindTo(cache);
cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));
MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);
95
return new ConstantCallSite(memoize);
96. 4. Bootstrap method (II)
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
ReflectiveOperationException {
MethodHandle target = lookup.findStatic(staticType, name, type);
HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);
String selector = name + type.toMethodDescriptorString();
HashMap<Object, Object> cache = cacheTable.get(selector);
if (cache == null) {
cache = new HashMap<Object, Object>();
cacheTable.put(selector, cache);
} Dla przypomnienia: zadaniem BSM jest utworzyd i skonfigurowad
miejsce wywołania (callsite). Skonfigurowad, czyli określid docelową
MethodHandle identity = MethodHandles.identity(type.returnType()); target.
metodę –
identity = identity.asType(identity.type().changeParameterType(0, Object.class));
Tutaj, utworzone miejsce wywołania, tj. jego target, mimo, iż będzie
identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
to całkiem skomplikowany łaocuch operacji, nie będzie się zmieniał,
więc jest typu ConstantCallSite.
MethodHandle update = UPDATE.bindTo(cache);
update = update.asType(type.insertParameterTypes(0, type.returnType()));
A to sprzyja optymalizacjom wykonywanym przez JIT.
Łaocuch kombinatorów musi zapewnid, że typ uchwytu zwracanego
MethodHandle fallback = MethodHandles.foldArguments(update, target);
fallback = MethodHandles.dropArguments(fallback, 0, Object.class); memoize) będzie zgodny z typem miejsca wywołania
z BSM (tu:
(czyli w tym przykładzie: (long)long, bo taki jest typ metody fib).
Ale ten BSM jest ładniejszy (bardziej generyczny) – nie ma
MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback); operacje w rodzaju
„zaszytych” tych typów – stąd
type.parameterType(0), type.returnType(), itp.
MethodHandle cacheQuerier = MAP_GET.bindTo(cache);
cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));
MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);
96
return new ConstantCallSite(memoize);
97. 4. Bootstrap method (II)
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
ReflectiveOperationException {
MethodHandle target = lookup.findStatic(staticType, name, type);
HashMap<String, HashMap<Object, Object>> cacheTable = cacheTables.get(staticType);
Argumentami foldArguments są uchwyty do metod. foldArguments działa w ten
String selector = name + type.toMethodDescriptorString();
sposób, że:
HashMap<Object, Object> cache = cacheTable.get(selector);
1. najpierw wywołuje uchwyt podany jako drugi argument (tu: cacheQuerier)
if (cache == null) {
2. jeśli zwróci on wynik (także null, ale nie void), to ten wynik jest wstawiany
cache = new HashMap<Object, Object>(); !) do listy argumentów na pozycji 0 pierwszego uchwytu (tu:
(INSERT
cacheTable.put(selector, cache); combiner)
} 3. wywoływany jest pierwszy uchwyt (tu: combiner)
Tutaj, cacheQuerier będzie najpierw odpytywał bufor (za pomocą metody
MethodHandle identity = MethodHandles.identity(type.returnType());
identity = identity.asType(identity.type().changeParameterType(0, Object.class));tego odpytywania (obiekt/wynik obliczeo lub
HashMap.get) i wstawiał wynik
null).
identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
Typ wynikowego uchwytu z foldArguments (tu: memoize) musi byd zgodny z
MethodHandle update = UPDATE.bindTo(cache); typem miejsca wywołania *tu: (long)long, bo taki jest typ metody fib+
update = update.asType(type.insertParameterTypes(0, type.returnType()));
MethodHandle fallback = MethodHandles.foldArguments(update, target);
fallback = MethodHandles.dropArguments(fallback, 0, Object.class);
MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);
MethodHandle cacheQuerier = MAP_GET.bindTo(cache);
cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));
MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);
97
return new ConstantCallSite(memoize);
98. 4. Bootstrap method (II)
public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) throws
ReflectiveOperationException { Zadaniem cacheQuerier jest odpytanie bufora (chod nie jestem przekonany
czy ‚cache’ to jest tutaj właściwa nazwa), czy dla danego argumentu (wartości
MethodHandle target = lookup.findStatic(staticType, name, type);
przekazanej do metody fib), który tutaj jest kluczem, została wcześniej
przypisana wartośd (rezultat wcześniejszego wywołania).
HashMap<String, HashMap<Object, Object>> cacheTable nie, to zwracany jest null.
Jeśli = cacheTables.get(staticType);
Jeśli tak, to zwracany jest obiekt (=wstawiony wcześniej, wynik wykonania
metody).
String selector = name + type.toMethodDescriptorString();
HashMap<Object, Object> cache = cacheTable.get(selector);
if (cache == null) { Wracając trochę do poprzedniego slajdu i metody foldArguments:
Uchwyt cacheQuerier w czasie wykonania (!) zawsze zwróci wynik różny od
cache = new HashMap<Object, Object>();
void (bo tak działa HashMap.get), więc jeśli chodzi o typowanie, to pierwszy
cacheTable.put(selector, cache); argument uchwytu combiner będzie pominięty (tak działa foldArguments).
} Czyli (tu) typ uchwytu combiner musi byd (Object, long)long *bo Object
będzie pominięty), czyli de facto (long)long, a zatem będzie zgodny z
wymaganiem miejsca wywołania (taki typ musi mied memoize).
MethodHandle identity = MethodHandles.identity(type.returnType());
identity = identity.asType(identity.type().changeParameterType(0, cacheQuerier musi mied typ (Object)long *bo taki jest wymóg
Kontynuując: Object.class));
foldArguments). cacheQuerier to uchwyt do metody HashMap.get, która ma
identity = MethodHandles.dropArguments(identity, 1, type.parameterType(0));
typ (Object)Object. A zatem, za pomocą metody asType, adaptujemy uchwyt
cacheQuerier, tak, aby spełnid wymóg foldArguments i „zaprezentowad”
MethodHandle update = UPDATE.bindTo(cache); cacheQuerier, jako uchwyt o typie: (Object)long.
update = update.asType(type.insertParameterTypes(0, type.returnType()));
Gra i buczy…
MethodHandle fallback = MethodHandles.foldArguments(update, target);
fallback = MethodHandles.dropArguments(fallback, 0, Object.class);
MethodHandle combiner = MethodHandles.guardWithTest(NOT_NULL, identity, fallback);
MethodHandle cacheQuerier = MAP_GET.bindTo(cache);
cacheQuerier = cacheQuerier.asType(MethodType.methodType(Object.class, type.parameterType(0)));
MethodHandle memoize = MethodHandles.foldArguments(combiner, cacheQuerier);
98
return new ConstantCallSite(memoize);