:: Enseignements :: ESIPE :: E4INFO :: 2018-2019 :: Java Inside ::
[LOGO]

MethodHandle et Inlining Cache


MethodHandle, bindTo, insertArguments, dropArguments, etc, Inlining Cache

Exercice 1 - MethodHandle

Le but de cet exercice est se familiariser avec la notion the method handle. En Java, il existe deux APIs pour effectuer des appels de méthode de façon réflexif, l'API de reflection (java.lang.reflect.Method.invoke) disponible depuis la version 1.1 de Java et l'API d'invocation (java.lang.invoke.MethodHandle.invokeExact) disponible depuis la version 7 de Java.

public class Example {
  private static String aStaticHello(int value) {
    return "question " + value;
  }
  private String anInstanceHello(int value) {
    return "question " + value;
  }
}
   

  1. Créer un répertoire lab06 dans le repository java-inside
    Ajouter la version de pro "jdk-12" à votre PATH
          export PATH=$PATH:/home/ens/forax/java-inside/pro-jdk-12
        

    Exécuter pro scaffold dans le répertoire lab06 pour obtenir les fichiers de départ.
  2. Créer un projet Eclipse pointant sur le répertoire lab06 et modifier le fichier YAML de Travis pour tester aussi le lab06.
  3. Dans une classe de test JUnit ExampleTests, écrire un test permettant de récupérer la méthode aStaticHello par reflection puis d'appeler celle-ci.
    Puis ajouter un autre test qui appele la méthode anInstanceHello.
  4. Ajouter un nouveau test qui au lieu d'utiliser l'API de reflection, utilise un objet MethodHandles.Lookup (créer en appeler la méthode statique lookup), un appel à privateLookupIn pour avoir accès au méthode privée d'une classe puis pour trouver la méthode static aStaticHello grâce à un findStatic pour obtenir un MethodHandle.
    Puis utiliser la méthode invokeExact pour appeler le method handle.
    Si il y a des erreurs bizarres RTFM !
  5. Créer au autre test qui utilise l'API d'invocation pour appeler la méthode d'instance anInstanceHello en utilisant findVirtual.
  6. Comparer l'API de reflection et l'API d'invocation
  7. Utiliser la méthode MethodHandles.insertArguments pour créer à partir d'un MethodHandle créer sur la méthode d'instance anInstanceHello un nouveau méthode handle dont l'argument est toujours 8. Ecrire un test permettant de tester ce nouveau method handle.
  8. Comment utiliser MethodHandles.dropArguments ? Ecrire un nouveau test vérifiant que dropArguments marche correctement.
  9. On veut utiliser l'un-boxing automatique pour que si l'on appel un method handle avec un Integer, celui-ci soit transformer en int. Ecrivez un test en utilisant la méthode d'instance methodHandle.asType().
  10. A quoi sert la méthode MethodHandle.constant ?
    Ecrire un nouveau test testant que la méthode MethodHandle.constant fonctionne correctement.
  11. A quoi sert la méthode MethodHandle.guardWithTest ?
    Ecrire un nouveau test testant que la méthode MethodHandle.guardWithTest fonctionne correctement en créant un method handle qui renvoie 1 si le paramètre du méthode handle est bien égal (equals) à "foo" et -1 sinon.
    Note: attention target et fallback doivent avoir la même signature.
    Note2: oui, il faut utiliser les méthodes handles vu précédemmment.

Exercice 2 - Inlining Cache

Comme vu dans un lab précédent, le switch sur les String fait un switch sur le hashCode des Strings ce qui oblige le compilateur et la machine virtuelle à utiliser le même algorithme pour String.hashCode.
On souhaite éviter ce problème même si cela veut dire avoir un code un peut moins efficace.
Pour cela, à partir d'un tableau de chaine de caractère, on va créer un method handle qui associe à chaque chaine de caractère son index dans le tableau. Dans le cas où l'on appel le method handle avec une chaine de caractère qui n'est pas présente dans le tableau, la valeur de retour doit être -1.

En supposant que la méthode stringSwitch existe
  public static MethodHandle stringSwitch(String... matches) {
    ...
  }
   
le code suivant doit être valide
    var mh = stringSwitch("foo", "bar", "bazz");
    assertEquals(0, (int)mh.invokeExact("foo"));
    assertEquals(1, (int)mh.invokeExact("bar"));
    assertEquals(-1, (int)mh.invokeExact("booze"));
   

  1. Ecrire le code de la méthode stringSwitch tel que le code du method handle fasse des if equals sur chaque élément du tableau.
  2. Si vous faites un findVirtual sur equals à chaque tour de boucle, changer votre code pour stocker le MethodHandle dans une variable static final.
    Note: il faut mettre l'initialisation dans le bloc static !
  3. Rappeler ce qu'est un inlining cache ?
  4. On se propose de fournir une nouvelle implantation, nommé stringSwitch2 qui utilise un MutableCallSite qui est une boite qui contient un method handle (que l'on obtient avec getTarget et que l'on peut changer avec setTarget).
    A partir d'un MutableCallSite, on peut obtenir un method handle (avec dynamicInvoker) qui si on l'appel va appeler le method handle obtenu par l'appel à getTarget.
    L'intérêt d'un MutableCallSite et que l'on est pas obliger de créer les if equals en avance mais on peut créer un if equals que si l'on demande quelle est l'index d'une chaine de caractère.
    Et pour des questions de performances, l'idée est de modifier le MutableCallSite de telle façon à ce que la méthode slowStringSwitch ne soit pas appelé si l'on voit une nouvelle fois la même chaine de caractère (et quelle fait partie des chaines de caractères du tableau).
      public static MethodHandle stringSwitch2(String... matches) {
        return new InliningCache(matches).dynamicInvoker(); 
      }
      
      static class InliningCache2 extends MutableCallSite {
        private static final MethodHandle SLOW_SWITCH_STRING;
        static {
          SLOW_SWITCH_STRING = ...
        }
        
        private final List<String> matches;
        
        public InliningCache2(String... matches) {
          super(methodType(int.class, String.class));
          this.matches = List.of(matches);
          setTarget(MethodHandles.insertArgument(SLOW_SWITCH_STRING, 0, this));
        }
        
        private int slowStringSwitch(String value) {
          // TODO
        }
      }
        
    Ecrivez le code nécessaire et du dupliquer votre test qui test stringSwitch pour qu'il test aussi stringSwitch2.
  5. On peut remarquer qu'il existe deux façon de mettre le if equals, soit on peut le mettre soit devant soit derrière les autres if equals existants.
    Sachant que statistiquement la première chaine de caractère que l'on demande est celle qui est le plus demandée, quelle est la meilleure implantation ?