201305 - lambda by r. forax

Post on 21-Jun-2015

440 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Dans les traboulesdes lambdas

Rémi ForaxUPEMMai 2012

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

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

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

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

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

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

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

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

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 !

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

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

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

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

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

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

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

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

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 !

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

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

Le coté sombre

>= JDK8< JDK 8

Où sont passées mes belles interfaces ?

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

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

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

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

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

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

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

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

Hunder the hoodHunder the hood

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

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

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

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

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

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

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

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

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

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

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

top related