0
INVOKEDYNAMIC = bardziej dynamiczna JVM                          Confitura 2012                             Waldek Kot    ...
Prezentacja mocno korzysta zanimacji, więc warto się przełączyd      na tryb „Slide Show”                                 ...
PrologNiniejsza prezentacja zawiera materiał, który chciałemzaprezentowad podczas konferencji Confitura 2012(http://confit...
Agenda1. Motywacja – dlaczego taki temat ?2. Trochę teorii o InvokeDynamic3. Praktyka  – zaczynając od „Hello World”     ...
Motywacjaczyli dlaczego zgłosiłem taki temat na Confiturę 2012 ?                                                          5
Motywacja pozytywnaInvokeDynamic (krócej: InDy) to największa           nowośd w Java 7                                   ...
Motywacja negatywna• bardzo mało informacji o InvokeDynamic   – szczególnie w polskim Internecie       • InvokeDynamic w g...
Dlaczego warto poznad InDy ?• nowy, fundamentalny składnik JVM   – nowy format klas (constant pool, .class)   – nowy bytec...
Po co InvokeDynamic ?Żeby łatwiej tworzyd dynamiczny, generyczny kod,w dodatku wydajnie wykonywany przez JVM.• z tego same...
Po co InvokeDynamic ?• … ale: wszystko co daje InDy można dzisiaj zrobid  innymi metodami („zasymulowad”)• prawda, tyle ty...
Ważna uwaga• większośd przykładów i kodu, który tutaj pokazuję ukradłem    – adaptacja filozofii: „najlepszy kod, to taki...
Krótka teoria InvokeDynamic                              12
InvokeDynamicPozwala programiście mied kontrolę nad takczymś tak elementarnym, jak wywoływanie                 metod      ...
MethodHandlewskaźnik / uchwyt / referencja do metody• „metoda” oznacza tutaj także operację dostępu  do pól (obiektów i kl...
MethodHandle - Hello World• do zabawy z większością tego co oferuje Indy  wystarczy Java 7 i IDE• czyli tyle wystarczy roz...
MethodHandle – Hello World                                                                    Do tworzenia uchwytów do met...
Więcej przykładów później                               17
MethodHandle• jest bardzo lekką konstrukcją   – (sorry za powtarzanie się) zrozumiałą dla JIT (a także GC) w JVM• niemal n...
MethodHandle - kombinatory• API udostępnione w ramach InvokeDynamic pozwala na  całkiem złożone „manipulacje” uchwytami do...
MethodType – typ metody• związany z MethodHandle• określa dokładny typ metod i uchwytów do metod   – czyli: typy parametró...
Nowy bytecode: INVOKEDYNAMICPierwsze w historii rozszerzenie listy instrukcji JVM !• chod można spekulowad, że o czymś tak...
Bytecode: INVOKEDYNAMIC• przy pierwszym napotkaniu tej instrukcji, JVM wywołuje metodę (tzw.  BSM – bootstrap method), któ...
CallSite• wynikiem wykonania się BSM jest utworzenie  obiektu klasy java.lang.invoke.CallSite• wewnątrz tego obiektu jest ...
CallSite• API udostępnia 3 specjalizowane klasy CallSite:   – ConstantCallSite – czyli informujemy JVM, że po wykonaniu   ...
Inne przydatne konstrukcjeAPI InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcjipomocniczych (prostszyc...
Czas na DEMO !(wiem, nareszcie )                      26
DEMO Iczyli zabawy z MethodHandle                              27
Co potrzeba ?• Java 7  – im wyższa wersja, tym lepszej wydajności    InvokeDynamic należy się spodziewad• IDE  – tworzymy ...
MethodHandle – Hello World (było)                                                                  Do tworzenia uchwytów d...
MethodHandle – identity                                                                                   Uchwyt do metody...
MethodHandle – typepublic class HelloIndyWorld {            public static void main(String[] args) throws Throwable {     ...
MethodHandle – invoke, invokeExactpublic class HelloIndyWorld {            public static void main(String[] args) throws T...
MethodHandle – Polymorphic Signature•   InvokeDynamic wprowadza także ciekawostkę w postaci tzw. polimorficznych sygnatur ...
MethodHandle – coś ciekawszego: uchwyt do własnej metodypublic class HelloIndyWorld {                                     ...
MethodHandle – uchwyt do metody wirtualnejpublic class HelloIndyWorld {               public static class MyClass {       ...
MethodHandle – uchwyt do metody wirtualnej (2)public class HelloIndyWorld {              public static class MyClass {    ...
MethodHandle – składanie uchwytów (kombinatory)public class HelloIndyWorld {               public static class MyClass {  ...
MethodHandle – filtrowanie wyników metodpublic class HelloIndyWorld {               public static class MyClass {         ...
MethodHandle – interceptor typu ‚before’public class HelloIndyWorld {      public static class MyClass {                pr...
MethodHandle – interceptor typu ‚before’ (poprawniej)public class HelloIndyWorld {      public static class MyClass {     ...
MethodHandle – interceptor typu ‚before’ (najpoprawniej )public class HelloIndyWorld {      public static class MyClass {...
MethodHandle – interceptor typu ‚after’public class HelloIndyWorld {      public static class MyClass {                pri...
MethodHandles / MethodHandleDostępne są także metody:• wstawiające argumenty wybranego typu (insertArgument)• zmieniające ...
MethodHandles• jest nawet dostępna konstrukcja IF-THEN-ELSE !   – MethodHandles.guardWithTest• szczegóły (m.in. dotyczące ...
MethodHandle – przykład z .guardWithTestpublic class HelloIndyWorld {               public static class MyClass {         ...
MethodHandle i refleksja (java.lang.reflect)public class HelloIndyWorld {           public static void main(String[] args)...
MethodHandleProxies• możliwe jest tworzenie obiektów, implementujących  podany interfejs (typu SAM, czyli z jedną metodą, ...
MethodHandleProxies - przykładpublic class HelloIndyWorld {              public interface MyInterface {                   ...
DEMO IIczyli wywołanie instrukcji bytecode’u         INVOKEDYNAMIC                                        49
Mały problem• niestety, ale w Java 7, w języku Java nie ma składni pozwalającej na  bezpośrednie tworzenie dynamicznych wy...
Generowanie instrukcji INVOKEDYNAMIC• W sieci jest kilka przykładów jak generowad instrukcję  INVOKEDYNAMIC   – http://web...
Wymagania• Tak jak poprzednio, czyli:  – Java 7  – IDE• Dodatkowo:  – ASM (http://forge.ow2.org/projects/asm)     • asm-al...
Github• Tutaj znajduje się kod źródłowy pokazywanych przykładów:    – https://github.com/waldekkot/Confitura2012_InvokeDyn...
Przykład (DemoIndySecond)public class HelloInDyWorld1 {     public static String myConfituraMethod(String s)   { return s ...
DemoIndySecond (krok I)         InvokeDynamic.prepare, przygotowuje („wstawia” do kodu Java) instrukcję                   ...
DemoIndySecond(krok II)public class HelloInDyWorld1 {     public static String myConfituraMethod(String s)     { return s ...
DemoIndySecond(krok III)public class HelloInDyWorld1 {     public static String myConfituraMethod(String s)     { return s...
DemoIndySecond(krok IV)public class HelloInDyWorld1 {     public static String myConfituraMethod(String s)   { return s + ...
InvokeDynamic.prepare• własny kod pomocniczy (z braku składni w Java dla InvokeDynamic)• pozwala utworzyd dynamiczne wywoł...
Klasa InvokeDynamic• jej zrozumienie nie jest szczególnie trudne, ale trzeba mied  elementarną wiedzę o ASM   – lub rozumi...
HelloIndyWorld2Przykład pokazuje, że do BSM można (z miejscawywołania !) przekazywad parametry, a tym samymskonfigurowad s...
DemoIndySecond.HelloIndyWorld2public class HelloInDyWorld2 {     public static String myConfituraMethod(String s) { return...
HelloIndyWorld3• przykład pokazuje jak uczynid kod korzystający  z dynamicznego wywoływania metod bardziej  „normalnym”• z...
DemoIndySecond.HelloIndyWorld3public class HelloInDyWorld3 {       public interface IExecutable {                   public...
DEMO IIIczyli krótki benchmark wywołao metod: InvokeDynamic vs. Static vs. Reflection                                     ...
Po co taki (głupawy w sumie) benchmark ? • bo dosyd często podnoszą się głosy, że taki   dynamiczny mechanizm wywoływania ...
DemoIndyThirdhttps://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyThird                           ...
Benchmark• 7-krotne wywołanie 100 mln razy poniższej metody:        public static long sumAndMultiply(long a, long b, int ...
Wyniki ?           69
Benchmark INVOKE DYNAMIC  1999800000000, TIME: 228 ms  1999800000000, TIME: 150 ms  1999800000000, TIME: 127 ms  199980000...
DEMO IVczyli przykład wykorzystania jednej z              cech BSM                                        71
Ale po co ?• pewnie wciąż zadajecie sobie pytanie, po co  ten cały InvokeDynamic ?• odpowiedź wciąż brzmi:  – aby pisad ba...
BSM• jak pamiętacie, BSM dla instrukcji  INVOKEDYNAMIC jest wykonywana dopiero  wtedy, gdy JVM taką instrukcję napotka• cz...
Przykład – Lazy Constant• załóżmy, że chcecie mied w swojej klasie stałą  wartośd (np. XML), ale inicjowaną poprzez  wykon...
Pomysł 1    Stała na poziomie klasy, inicjowana w bloku statycznympublic class ConstantResourceImpl implements ConstantRes...
Zadziała ?                                  LazyConstantsMain.javaZadziała, ale inicjalizacja tej stałej (czyli wykonanie ...
Pomysł 2      (bardziej lazy, a w zasadzie to very lazy)• „dostęp do wartości stałej” = „wykonanie metody zwracającej (sta...
public class ConstantResourceImplWithIndy implements ConstantResource {      private MethodHandle mh;     public static Ca...
Zadziała ?                          LazyConstantsMainWithIndy.javaZadziała !public class LazyConstantsMainWithIndy {      ...
A gdy użyjemy stałej…public class LazyConstantsMainWithIndy {            public static void main(String[] args) {         ...
Ten sam trick może byd użyteczny w   wielu Waszych programach…                                     81
Github• DemoIndyFourthhttps://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyFourth                 ...
DEMO Vczyli przyspiesz swój kod rekurencyjny(a tak naprawdę, to: jak czerpad intelektualną przyjemnośd z         czytania ...
Czy masz pomysł jak przyspieszyd ten kod ?    Klasyka: obliczanie sumy N pierwszych liczb z ciągu Fibonacciegopublic class...
Dlaczego ten kod działa wolno ?• ten kod jest piękny – bardzo przejrzyście  wyraża intencję programisty• ale (w większości...
Memoization• wykorzystamy dynamiczne wywoływanie metod, aby ten  piękny i przejrzysty kod przyspieszyd   – nie naruszając ...
InvokeDynamic a memoizationInvokeDynamic dostarcza nam kilku ważnych elementów• w dalszej części prezentacji po kolei je o...
Ale zanim nastąpią wyjaśnienia,             zobaczcie efekt koocowy !SpeedRecurenceWithIndy.java                          ...
Zresztą, w zasadzie to nawetzmieniliśmy złożonośd obliczeniową tej               metody!  [z wykładniczej na niemal stałą ...
OK, to na czym polegają różnice ?                                    90
1. Zamiast wywołao fib(n) użycie InvokeDynamicpublic class SpeedRecurenceWithIndy {      private static MethodHandle mhDyn...
2. Bufor do przechowywania wynikówprivate static ClassValue<HashMap<String, HashMap<Object, Object>>> cacheTables = new Cl...
3. Metody pomocnicze dla BSM (i ich uchwyty)Rzeczy typu:• NOT_NULL: sprawdzenie czy podany obiekt nie jest null• MAP_GET, ...
4. Bootstrap method (I)Aaaaaaby zamienid statyczne wywołania * fib() + na dynamiczne, wystarczyłoby tyle:public static Cal...
4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) thr...
4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) thr...
4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) thr...
4. Bootstrap method (II)public static CallSite myBSM(Lookup lookup, String name, MethodType type, Class<?> staticType) thr...
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
Upcoming SlideShare
Loading in...5
×

INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)

335

Published on

Slajdy z konferencji Confitura 2012 (http://confitura.pl), z sesji pt.: INVOKEDYNAMIC - bardziej dynamiczna JVM.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
335
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Transcript of "INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)"

  1. 1. INVOKEDYNAMIC = bardziej dynamiczna JVM Confitura 2012 Waldek Kot waldek.kot@gmail.comwersja 1.1 1
  2. 2. Prezentacja mocno korzysta zanimacji, więc warto się przełączyd na tryb „Slide Show” 2
  3. 3. PrologNiniejsza prezentacja zawiera materiał, który chciałemzaprezentowad 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. 4. Agenda1. Motywacja – dlaczego taki temat ?2. Trochę teorii o InvokeDynamic3. Praktyka – zaczynając od „Hello World”  4
  5. 5. Motywacjaczyli dlaczego zgłosiłem taki temat na Confiturę 2012 ? 5
  6. 6. Motywacja pozytywnaInvokeDynamic (krócej: InDy) to największa nowośd w Java 7 6
  7. 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. 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. 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. 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. 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
  12. 12. Krótka teoria InvokeDynamic 12
  13. 13. InvokeDynamicPozwala programiście mied kontrolę nad takczymś tak elementarnym, jak wywoływanie metod 13
  14. 14. MethodHandlewskaź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. 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. 16. MethodHandle – Hello World Do tworzenia uchwytów do metod używamy m.in. metod (statycznych) klasypackage 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
  17. 17. Więcej przykładów później  17
  18. 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. 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. 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. 21. Nowy bytecode: INVOKEDYNAMICPierwsze 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. 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. 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. 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. 25. Inne przydatne konstrukcjeAPI InvokeDynamic udostępnia jeszcze kilka innych przydatnych konstrukcjipomocniczych (prostszych i/lub bardziej wydajnych niż gdyby samodzielniebudowad 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
  26. 26. Czas na DEMO !(wiem, nareszcie ) 26
  27. 27. DEMO Iczyli zabawy z MethodHandle 27
  28. 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. 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: typuimport 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. 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. 31. MethodHandle – typepublic 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. 32. MethodHandle – invoke, invokeExactpublic 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. 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. 34. MethodHandle – coś ciekawszego: uchwyt do własnej metodypublic 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. 35. MethodHandle – uchwyt do metody wirtualnejpublic 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. 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. 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. 38. MethodHandle – filtrowanie wyników metodpublic 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. 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. 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. 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. 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. 43. MethodHandles / MethodHandleDostę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. 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. 45. MethodHandle – przykład z .guardWithTestpublic 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. 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. 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. 48. MethodHandleProxies - przykładpublic 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
  49. 49. DEMO IIczyli wywołanie instrukcji bytecode’u INVOKEDYNAMIC 49
  50. 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. 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. 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. 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
  54. 54. Przykład (DemoIndySecond)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); } 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") ); }} 54
  55. 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. 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. 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
  58. 58. DemoIndySecond(krok IV)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); Dynamiczne wywołanie metody. } 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") ); }} 58
  59. 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. 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. 61. HelloIndyWorld2Przykład pokazuje, że do BSM można (z miejscawywołania !) przekazywad parametry, a tym samymskonfigurowad sposób wywoływania metod z tegomiejsca – dynamizm w akcji ! 61
  62. 62. DemoIndySecond.HelloIndyWorld2public class HelloInDyWorld2 { public static String myConfituraMethod(String s) { return s + " 2012"; } public static String myConfituraMethodNext(String s) { return s + " 2013"; } public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) { MethodHandle mh = null; try { if((bsmArgs != null) && (bsmArgs.length > 0) && ("2013".equals(bsmArgs[0]))) mh = MethodHandles.lookup().findStatic(HelloInDyWorld2.class, "myConfituraMethodNext", MethodType.methodType(String.class, String.class)); else mh = MethodHandles.lookup().findStatic(HelloInDyWorld2.class, "myConfituraMethod", MethodType.methodType(String.class, String.class)); } catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); } return new ConstantCallSite(mh); } public static void main(String args[]) throws Throwable { MethodHandle mh = InvokeDynamic.prepare("run me", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld2.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class)); System.out.println(mh.invoke("Confitura")); MethodHandle mh2 = InvokeDynamic.prepare("run me two", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld2.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class), "2013"); System.out.println( mh2.invoke("Confitura") ); }} 62
  63. 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
  64. 64. DemoIndySecond.HelloIndyWorld3public class HelloInDyWorld3 { public interface IExecutable { public String execute(String s); } public static String myConfituraMethod(String s) { return s + " 2012"; } public static String myConfituraMethodNext(String s) { return s + " 2013"; } public static CallSite myBSM(MethodHandles.Lookup caller, String methodName, MethodType methodType, Object... bsmArgs) { MethodHandle mh = null; try { if((bsmArgs != null) && (bsmArgs.length > 0) && ("2013".equals(bsmArgs[0]))) mh = MethodHandles.lookup().findStatic(HelloInDyWorld3.class, "myConfituraMethodNext", MethodType.methodType(String.class, String.class)); else mh = MethodHandles.lookup().findStatic(HelloInDyWorld3.class, "myConfituraMethod", MethodType.methodType(String.class, String.class)); } catch (NoSuchMethodException | IllegalAccessException e) { e.printStackTrace(); } return new ConstantCallSite(mh); } public static void main(String args[]) throws Throwable { IExecutable myObj = InvokeDynamic.prepareAs(IExecutable.class, "run me", MethodType.methodType(String.class, String.class), "myBSM", HelloInDyWorld3.class, MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class)); System.out.println( myObj.execute("Hello !") ); }} 64
  65. 65. DEMO IIIczyli krótki benchmark wywołao metod: InvokeDynamic vs. Static vs. Reflection 65
  66. 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
  67. 67. DemoIndyThirdhttps://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyThird 67
  68. 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 }
  69. 69. Wyniki ? 69
  70. 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 msBenchmark 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
  71. 71. DEMO IVczyli przykład wykorzystania jednej z cech BSM 71
  72. 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. 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. 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. 75. Pomysł 1 Stała na poziomie klasy, inicjowana w bloku statycznympublic 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. 76. Zadziała ? LazyConstantsMain.javaZadziała, ale inicjalizacja tej stałej (czyli wykonanie tego mega-złożonegoparsowania) wykona się owszem raz, ale niezależnie od tego, czy tej stałej wogó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. 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. 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. 79. Zadziała ? LazyConstantsMainWithIndy.javaZadział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żoneparsowanie nie odbywa się. 79
  80. 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. 81. Ten sam trick może byd użyteczny w wielu Waszych programach… 81
  82. 82. Github• DemoIndyFourthhttps://github.com/waldekkot/Confitura2012_InvokeDynamic/tree/master/DemoIndyFourth 82
  83. 83. DEMO Vczyli 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. 84. Czy masz pomysł jak przyspieszyd ten kod ? Klasyka: obliczanie sumy N pierwszych liczb z ciągu Fibonacciegopublic 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. 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. 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. 87. InvokeDynamic a memoizationInvokeDynamic 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. 88. Ale zanim nastąpią wyjaśnienia, zobaczcie efekt koocowy !SpeedRecurenceWithIndy.java 88
  89. 89. Zresztą, w zasadzie to nawetzmieniliś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.
  90. 90. OK, to na czym polegają różnice ? 90
  91. 91. 1. Zamiast wywołao fib(n) użycie InvokeDynamicpublic 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. 92. 2. Bufor do przechowywania wynikówprivate 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. 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. 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. 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. 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. 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. 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);
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×