Anzeige

201305 - Lambda by R. Forax

lyonjug
22. May 2013
Anzeige

Más contenido relacionado

Presentaciones para ti(20)

Anzeige
Anzeige

201305 - Lambda by R. Forax

  1. Dans les traboules des lambdas Rémi Forax UPEM Mai 2012
  2. 2 Moi MCF à l'Université Paris Est Marne-la-vallée Joue avec Java depuis trop longtemp pour l'avouer Créateur de langages dynamiques ou pas Expert pour les JSR 292 (invokedynamic) et JSR 335 (lambda) Contributeur OpenJDK, ASM, Tatoo, PHP.reboot, JDart, etc...
  3. 3 Un exemple de lambda enum Gastronomy { rosette, andouillette, coq_au_vin, tripe, cardoon } private static List<Gastronomy> getGastronomyList() { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); Collections.sort(list, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list; } public static void main(String[] args) { System.out.println(getGastronomyList()); }
  4. 4 Exemple un poil plus compliqué private static ArrayList<Gastronomy> prefixList(String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2; } private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2; }
  5. 5 Parties codantes private static ArrayList<Gastronomy> prefixList(String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2; } private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2; }
  6. 6 Closure ?? Expression que l'on veut voir comme une valeur Les closures/lambda existent dans plein d'autres langages Lisp/Clojure, Ruby, Groovy, Scala, C#, JavaScript/Python, et même C++/Objective C récemment En Java, on a des classes annonymes Youpi ! Nan, je rigole :)
  7. 7 Problème des classes anonymes Verbeux visuellement rapport signal/bruit pas terrible Sémantiquement bizarre on veut envoyer une expression, créont une classe ... Perf discutable (pour des closures) création d'une instance à chaque appel + 1 classe sur le disque + 1 instance de java.lang.Class et ses métadatas en mémoire
  8. 8 Exemple de lambdas (sans lambdas) private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, new Comparator<Gastronomy>() { @Override public int compare(Gastronomy g1, Gastronomy g2) { return g1.name().compareTo(g2.name()); } }); return list2; }
  9. 9 Exemple de lambdas (avec lambdas) private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> { return g1.name().compareTo(g2.name()); }); return list2; } La syntaxe utilise -> (light arrow) et pas => (fat arrow) comme en Scala ou en C#
  10. 10 Inférence de type ! Le compilateur peut calculer le type des paramétres d'une lambda ! la méthode sort() prend une liste de Gastronomy donc elle attends un Comparator<Gastronomy> private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (Gastronomy g1, Gastronomy g2) -> { return g1.name().compareTo(g2.name()); }); return list2; } Le compilo n'utilise pas l'expression de la lambda pour faire l'inférence !
  11. 11 Exemple de lambdas (avec lambdas + inférence) private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); Collections.sort(list2, (g1, g2) -> g1.name().compareTo(g2.name()) ); return list2; }
  12. 12 Syntaxe: Lambda expression sans paramètre () -> System.out.println("Vive l'OL") avec un paramètre (+ inférence) employee -> employee.isManager() avec plusieurs paramètres en déclarant les types (int x, int y) -> x == y sans déclarer les types (+ inférence) (x, y) -> x == y
  13. 13 Syntaxe: Lambda instruction sans paramètre () -> { System.out.println("Vive l'OL"); } avec un paramètre (+ inférence) employee -> { return employee.isManager(); } avec plusieurs paramètres (+ inférence) (index1, index2) -> { list.set(index1, list.get(index2)); }
  14. 14 Sémantique Typé par une functional interface (ex SAM) Runnable, Callable, Filter, Function, ... Une lambda n'est pas un objet “this” représente la classe courante pas la lambda Une lambda est convertissable en un objet qui implante une functional interface Runnable r = () -> System.out.println("LOL?") objet pas objet
  15. 15 Et les collections ? Ici on filtre une liste, donc au lieu de private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2; }
  16. 16 Et les collections ? On aimerait bien écrire private static List<Gastronomy> prefixList( String prefix, List<Gastronomy> list) { return list.filter(g -> g.name().startsWith(prefix)) .toList() } Operation lazy Operation pas lazy Heu, toList() sur une liste, vraiment ??
  17. 17 Programmation déclarative list.filter(...) .map(...) .reduce(...) on veut éviter les structures de données intermédiaire lambdas list filter map reduce list filter map reduce Création (pipeline) exécution
  18. 18 Programmation déclarative Permet la programmation parallèle On split les données en amont en on envoie le calcul sur différentes threads ● Disponible pour Java 8 Permet d'utiliser les GPUs On émet le code PTX/HSAIL correspondant ● Projet Sumatra (prototype avec Graal) With great power there must also come — great responsibility! L'utilisateur ne doit pas faire d'effet de bord dans les lambdas !!
  19. 19 Interface java.util.Stream Sequentielle ou parallele list.stream() ou list.parallelStream() Deux types de methode intermédiaire filter, map, sorted, distinct, flatMap ... terminales forEach, reduce, collect, findFirst ... L'implantation n'utilise pas de structures intermédaires mais un pipeline ! Un effet de bord, t'es mort !
  20. 20 Sans l'API des streams private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) { ArrayList<Gastronomy> list2 = new ArrayList<>(); for(Gastronomy gastronomy: list) { if (gastronomy.name().startsWith(prefix)) { list2.add(gastronomy); } } return list2; }
  21. 21 Avec l'API des streams private static ArrayList<Gastronomy> prefixList( String prefix, List<Gastronomy> list) { return list.stream() .filter(g -> g.name().startsWith(prefix)) .collect(Collectors.toList()); } Bonus! pas besoin de déclarer la variable locale final !
  22. 22 Le coté sombre >= JDK8< JDK 8 Où sont passées mes belles interfaces ?
  23. 23 Traits Pour ajouter le support des lambdas au collections Les méthodes stream() et parallelStream() Il faut pouvoir ajouter des méthodes à une interface Il faut mettre du code dans les interfaces Don't panic Cela s'appelle des traits (trivia: les traits de Scala ne sont pas des traits)
  24. 24 Default methods Une méthode qui a du code dans une interface doit être taggée default Une méthode par défaut est utilisée par une classe si il n'y a pas de méthode définie ou fournie par une sous classe public interface Iterator<T> { public abstract boolean hasNext(); public abstract T next(); public default void remove() { throw new UnsupportedOperationException(); } }
  25. 25 Et le problème du diamand Une interface accepte du code mais pas de champs Pas de problème de redéfinition de champ donc Problème si on implante deux interfaces qui ont chacune une méthode ayant du code – Si une interface hérite de l'autre, on prend la méthode de la sous-interface – Sinon, le compilo plante interface I { default void m() { } } interface J { default void m() { } } class A implements I, J { // compile pas }
  26. 26 Super sur des interfaces La syntaxe, I.super.m() permet d'appeler la méthode m de l'interface I. donc pour résoudre le conflit interface I { default void m() { } } interface J { default void m() { } } class A implements I, J { public void m() { I.super.m(); } }
  27. 27 Retrofit Avec Java 8, l'interface java.util.List possède enfin une méthode sort (par défaut) qui appele Collections.sort :) private static List<Gastronomy> getGastronomyList(String prefix) { List<Gastronomy> list = Arrays.asList(Gastronomy.values()); ArrayList<Gastronomy> list2 = prefixList(prefix, list); list2.sort((g1, g2) -> g1.name().compareTo(g2.name())); return list2; }
  28. 28 Avoir une méthode intermédiaire n'est plus nécessaire, on peut tout écrire dans une seule méthode Et en utilisant Comparators.comparing() private static List<Gastronomy> getGastronomyList(String prefix) { return Arrays.stream(Gastronomy.values()) .filter(g -> g.name().startsWith(prefix)) .sorted(Comparators.comparing(g -> g.name())) .collect(Collectors.toList()); } public static void main(String[] args) { System.out.println(getGastronomyList("c")); } Exemple (suite)
  29. 29 Method Reference Il existe une syntaxe spécifique pour les lambdas qui appel juste une méthode g -> g.name() peut s'écrire Gastronomy::name On ne spécifie pas le type des paramètres, le compilateur utilise la fonctional interface pour les trouver Il est aussi possible de capturer une valeur Callable<String> c = "foo"::toUpperCase() System.out.println(c.call()); // prints FOO
  30. 30 Avec des Method References Et tout dans le main. public static void main(String[] args) { Arrays.stream(Gastronomy.values()) .filter(g -> g.name().startsWith("c")) .sorted( Comparators.comparing(Gastronomy::name)) .forEach(System.out::println); }
  31. 31 Hunder the hoodHunder the hood
  32. 32 Compilation - Création Une référence sur une méthode est crée en utilisant invokedynamic BiFunction<String, Integer, Character> fun = String::charAt; est transformé par le compilateur en code: 0: invokedynamic lambda() #0 ()Ljava/util/function/BiFunction; 5: astore_1
  33. 33 Compilation - Création constants: #0: #15 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invok e/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHan dle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #16 invokeinterface java/util/function/BiFunction.apply :(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; #17 invokevirtual java/lang/String.charAt:(I)C #18 (Ljava/lang/String;Ljava/lang/Integer;)Ljava/lang/Character; code: 0: invokedynamic lambda() #0 ()Ljava/util/function/BiFunction; 5: astore_1 Functional interface Methode de l'interface Methode référencée Signature générique réifiée
  34. 34 Intérêt d'utiliser invokedynamic La création de l'objet implantant la functional interface n'est pas écrite dans le code généré Peux changer en fonction des versions du JDK, des optimisations implantées dans la VM Si l'objet est constant, il peut être ré-utilisé Pour le jdk8, utilise ASM pour générer une classe proxy dynamiquement et sans vérification du bytecode
  35. 35 Compilation - Appel L'appel se fait en utilisant la méthode de l'interface BiFunction<String, Integer, Character> fun = ... char c = fun.apply("foo", 0); et en bytecode 7: ldc #3 // String “foo” 9: iconst_0 10: invokestatic #4 // Integer.valueOf:(I)Ljava/lang/Integer; 13: invokeinterface #5, 3 // BiFunction.apply (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 18: checkcast #6 // class Character 21: invokevirtual #7 // Character.charValue:()C 24: istore_2
  36. 36 Et les perfs ? / un nano-test public class HoodPerf { private static int COUNTER; private static void test() { BiFunction<String, Integer, Character> fun = String::charAt; char c = fun.apply("foobar", 5); COUNTER += c; // side effect } public static void main(String[] args) { for(int i=0; i<10_000; i++) { test(); } System.out.println(COUNTER); System.out.println(end – start); } } Ce code ne marche pas pour faire un benchmark mais ...
  37. 37 pour voir de l'assembleur ... {0x00007fd655851530} 'test' '()V' in 'HoodPerf' ...d64d074300: mov %eax,-0x14000(%rsp) ...d64d074307: push %rbp ...fd64d074308: sub $0x20,%rsp ...d64d07430c: mov $0xef010e68,%r10 ; {oop(a 'HoodPerf$$Lambda$1')} ...d64d074316: mov 0x8(%r10),%r8d ; getClass ...d64d07431a: mov $0xeeedaf80,%r11 ; {oop(a 'java/lang/Integer'[256] )} ...d64d074324: mov 0x210(%r11),%r11d ;*aaload ; - java.lang.Integer::valueOf@21 (line 809) ...d64d07432b: cmp $0xc6860240,%r8d ; {metadata('HoodPerf$$Lambda$1')} ...d64d074332: jne ...d64d0743e9 ;*invokeinterface apply ; - HoodPerf::test@13 (line 10) ...d64d074338: mov 0xc(%r11),%ebp ;*getfield value ; - java.lang.Integer::intValue@1 (line 871) ; implicit exception: dispatches to 0x00007fd64d074486 ...d64d07433c: test %ebp,%ebp ...d64d07433e: jl ...d64d074401 ;*iflt ; - java.lang.String::charAt@1 (line 650) ...d64d074344: cmp $0x3,%ebp ...d64d074347: jge ...d64d074401 ;*if_icmplt ; - java.lang.String::charAt@10 (line 650) ...d64d07434d: cmp $0x3,%ebp ...d64d074350: jae ...d64d0743b7 ahah charAt
  38. 38 Pour info, le code de String.charAt public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIOOBException(index); } return value[index]; } ...070d06a53c: test %edx,%edx ...070d06a53e: jl ...070d06a575 ;*iflt ; - java.lang.String::charAt@1 (line 650) ...070d06a540: mov 0xc(%rsi),%ebp ;*getfield value ; - java.lang.String::charAt@6 (line 650) ...070d06a543: mov 0xc(%rbp),%r10d ;*arraylength ; - java.lang.String::charAt@9 (line 650) ; implicit exception: dispatches to ...070d06a589 ...070d06a547: cmp %r10d,%edx ...070d06a54a: jge ...070d06a575 ;*if_icmplt ; - java.lang.String::charAt@10 (line 650) ...070d06a54c: cmp %r10d,%edx ...070d06a54f: jae ...070d06a562 ...070d06a551: movzwl 0x10(%rbp,%rdx,2),%eax
  39. 39 et encore de l'assembleur ... ...d64d074352: mov $0xef015e10,%r10 ; {oop([C)} ...d64d07435c: movzwl 0x10(%r10,%rbp,2),%ebp ;*caload ; - java.lang.String::charAt@27 (line 653) ...d64d074362: cmp $0x7f,%ebp ...d64d074365: jg ...d64d074411 ;*if_icmpgt ; - java.lang.Character::valueOf@3 (line 4570) ...d64d07436b: cmp $0x80,%ebp ...d64d074371: jae 0x00007fd64d0743c9 ...d64d074373: mov $0xeef0e828,%r10 ; {oop(a 'java/lang/Character'[128] )} ...d64d07437d: mov 0x10(%r10,%rbp,4),%r10d ;*aaload ; - java.lang.Character::valueOf@10 (line 4571) ...d64d074382: test %r10d,%r10d ...d64d074385: je ...d64d0743d9 ;*invokevirtual charValue ...d64d074387: mov $0xeef05ab8,%r11 ; {oop(a 'java/lang/Class' = 'HoodPerf')} ...d64d074391: mov 0x58(%r11),%r11d ;*invokestatic valueOf ...d64d074395: movzwl 0xc(%r10),%r10d ...d64d07439a: add %r10d,%r11d ...d64d07439d: mov $0xeef05ab8,%r10 ; {oop(a 'java/lang/Class' = 'HoodPerf')} ...d64d0743a7: mov %r11d,0x58(%r10) ;*putstatic COUNTER ...d64d0743ab: add $0x20,%rsp ...d64d0743af: pop %rbp ...d64d0743b0: test %eax,0x9006c4a(%rip) # ...d65607b000 ; {poll_return} ...d64d0743b6: retq Ça sert à quoi ?? charAt
  40. 40 ... en résumé Une méthode référence (ou une lambda) qui ne fait pas de capture est une constante L'appel a une lambda peut être dévirtualisé et inliné comme n'importe quel appel Le code montré est celui générer par le jdk8b88-lambda avec elision du boxing/unboxing desactivé :(
  41. 41 What's next ? Updates du JDK8 lambda constante sans check de classe lambda non-constante mieux dévirtualisée Java 9 Value Object Integer, Float, etc ne devrait pas avoir d'identité => pas sûr que cela marche Utilisation du/des GPUs dans les Stream parallele
  42. 42 Questions ? JDK8 Feature Freeze jeudi prochain ! les lambdas sont déjà dans le JDK ! https://jdk8.java.net/download.html Remonter les bugs sur la mailing list: lambda-dev http://mail.openjdk.java.net/mailman/listinfo/lambda-dev ou bugs.sun.com
Anzeige