201305 - lambda by r. forax

42
Dans les traboules des lambdas Rémi Forax UPEM Mai 2012

Upload: lyonjug

Post on 21-Jun-2015

440 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: 201305 - Lambda by R. Forax

Dans les traboulesdes lambdas

Rémi ForaxUPEMMai 2012

Page 2: 201305 - Lambda by R. Forax

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...

Page 3: 201305 - Lambda by R. Forax

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());}

Page 4: 201305 - Lambda by R. Forax

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;}

Page 5: 201305 - Lambda by R. Forax

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;}

Page 6: 201305 - Lambda by R. Forax

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 :)

Page 7: 201305 - Lambda by R. Forax

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

Page 8: 201305 - Lambda by R. Forax

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;}

Page 9: 201305 - Lambda by R. Forax

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#

Page 10: 201305 - Lambda by R. Forax

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 Gastronomydonc 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 !

Page 11: 201305 - Lambda by R. Forax

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;}

Page 12: 201305 - Lambda by R. Forax

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

Page 13: 201305 - Lambda by R. Forax

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));}

Page 14: 201305 - Lambda by R. Forax

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 interfaceRunnable r = () -> System.out.println("LOL?")

objet pas objet

Page 15: 201305 - Lambda by R. Forax

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;}

Page 16: 201305 - Lambda by R. Forax

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 lazyOperation pas lazy

Heu, toList() sur une liste, vraiment ??

Page 17: 201305 - Lambda by R. Forax

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

Page 18: 201305 - Lambda by R. Forax

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 dansles lambdas !!

Page 19: 201305 - Lambda by R. Forax

19

Interface java.util.Stream

Sequentielle ou parallelelist.stream() ou list.parallelStream()

Deux types de methodeintermédiaire

filter, map, sorted, distinct, flatMap ...

terminalesforEach, reduce, collect, findFirst ...

L'implantation n'utilise pas de structures intermédaires mais un pipeline !

Un effet de bord, t'es mort !

Page 20: 201305 - Lambda by R. Forax

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;}

Page 21: 201305 - Lambda by R. Forax

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 !

Page 22: 201305 - Lambda by R. Forax

22

Le coté sombre

>= JDK8< JDK 8

Où sont passées mes belles interfaces ?

Page 23: 201305 - Lambda by R. Forax

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)

Page 24: 201305 - Lambda by R. Forax

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(); }}

Page 25: 201305 - Lambda by R. Forax

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}

Page 26: 201305 - Lambda by R. Forax

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(); }}

Page 27: 201305 - Lambda by R. Forax

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;}

Page 28: 201305 - Lambda by R. Forax

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)

Page 29: 201305 - Lambda by R. Forax

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

Page 30: 201305 - Lambda by R. Forax

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);}

Page 31: 201305 - Lambda by R. Forax

31

Hunder the hoodHunder the hood

Page 32: 201305 - Lambda by R. Forax

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

Page 33: 201305 - Lambda by R. Forax

33

Compilation - Création

constants:#0: #15 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;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

Methodede l'interface

Methoderéférencée

Signature générique réifiée

Page 34: 201305 - Lambda by R. Forax

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

Page 35: 201305 - Lambda by R. Forax

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 Character21: invokevirtual #7 // Character.charValue:()C24: istore_2

Page 36: 201305 - Lambda by R. Forax

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 marchepas pour faire unbenchmark mais ...

Page 37: 201305 - Lambda by R. Forax

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

Page 38: 201305 - Lambda by R. Forax

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

Page 39: 201305 - Lambda by R. Forax

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

Page 40: 201305 - Lambda by R. Forax

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é :(

Page 41: 201305 - Lambda by R. Forax

41

What's next ?

Updates du JDK8

lambda constante sans check de classe

lambda non-constante mieux dévirtualisée

Java 9

Value ObjectInteger, Float, etc ne devrait pas avoir d'identité

=> pas sûr que cela marche

Utilisation du/des GPUs dans les Stream parallele

Page 42: 201305 - Lambda by R. Forax

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-devhttp://mail.openjdk.java.net/mailman/listinfo/lambda-dev

ou bugs.sun.com