Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Panama, un pont vers le code natif (ParisJug, Nov 2021)

Panama, un pont vers le code natif (ParisJug, Nov 2021)

Depuis toujours, en Java, interagir avec le code natif est une nécessité. JNI est présent depuis les débuts de la JVM pour faire ce pont. Malgré ses défauts, c'est une technologie qui fonctionne bien. D’autres approches sont apparues pour combler ces défauts: JNA, puis JNR. Les ingénieurs de l’OpenJDK ont profité de l’arrivée des MethodHandles pour réviser cette interaction avec le monde natif pour plus de facilité et de sécurité. Ce travail est connu sous le nom de Project Panama et cette présentation vous introduira à ces nouvelles API.

Vidéo 🇫🇷

Brice Dutheil

November 10, 2021
Tweet

More Decks by Brice Dutheil

Other Decks in Technology

Transcript

  1. None
  2. Panama Interconnecting Java with the native world.

  3. $ whoami Brice Dutheil $ jobs Datadog / Senior Software

    Engineer Blablacar / Senior Software Engineer Libon / Software Engineer :█
  4. https://www.oracle.com/a/ocom/img/obic-java-cup.svg https://upload.wikimedia.org/wikipedia/commons/1/18/OpenJDK_logo.svg 17

  5. Le projet Panama atteint une nouvelle étape dans le JDK

    17 ...toujours en incubation pour le JDK 18 • JEP-412 Foreign Function & Memory API (Incubator) aka l’appel de fonctions étrangères au code Java • JEP-414 Vector API (Second Incubator) aka l’API pour faire du calcul vectoriel
  6. Qui a déjà utilisé du code natif depuis la JVM

    ?
  7. Ce que je souhaite aborder ce soir • Le projet

    panama, c’est quoi ? • Appeler du code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  8. Ce que je souhaite aborder ce soir • Le projet

    panama, c’est quoi ? • Appeler du code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  9. Pourquoi Panama ? 1. Les libraries “natives” ◦ Omniprésente ◦

    Fonction de l’OS, device, file system, réseau, fonctionnalités tierces par d’autres libraries 2. Interaction Java ⇔ Natif ardue ◦ JNI, complexe à prendre en main, Possiblement: difficile à écrire du code correct et performant ◦ Tant sur l’usage de l’API que du transfert de donnée À quoi doit-il succéder ? → JNI, JNA, JNR-FFI
  10. Les technos actuelles : JNI À l’origine JNI (Java Native

    Interface) • JNI est présent dans la JVM dès la version 1.1 • Le code (vu de Java) ressemble à ça package q.r.s; class NativeBinding { static { System.load("{path/to/libNativeBinding.so}"); } // (1) public static native boolean isatty(int fileDescriptor); // (2) }
  11. Les technos actuelles : JNI À l’origine JNI (Java Native

    Interface) • JNI est présent dans la JVM dès la version 1.1 • Le code (vu de Java) ressemble à ça package q.r.s; class NativeBinding { static { System.load("{path/to/libNativeBinding.so}"); } // (1) public static native boolean isatty(int fileDescriptor); // (2) }
  12. Les technos actuelles : JNI À l’origine JNI (Java Native

    Interface) • JNI est présent dans la JVM dès la version 1.1 • Le code (vu de Java) ressemble à ça package q.r.s; class NativeBinding { static { System.load("{path/to/libNativeBinding.so}"); } // (1) public static native boolean isatty(int fileDescriptor); // (2) }
  13. Les technos actuelles : JNI Il faut extraire des headers

    C/C++ de la classe Java $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java 💡 Avant le JDK10 il faut utiliser javah. Voir JEP-313
  14. Les technos actuelles : JNI Il faut extraire des headers

    C/C++ de la classe Java Coté natif il faut écrire du code de glue #include "q_r_s_NativeBinding.h" // (1) #include <unistd.h> JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty // (2) (JNIEnv *env, jclass cls, jint fileDescriptor) { return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE; } $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java 💡 Avant le JDK10 il faut utiliser javah. Voir JEP-313
  15. Les technos actuelles : JNI Il faut extraire des headers

    C/C++ de la classe Java Coté natif il faut écrire du code de glue Il faut compiler séparément #include "q_r_s_NativeBinding.h" // (1) #include <unistd.h> JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty // (2) (JNIEnv *env, jclass cls, jint fileDescriptor) { return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE; } $ gcc \ -I $JAVA_HOME/include \ # (1) -I $JAVA_HOME/include/linux \ # (2) -fPIC \ # (3) -shared \ # (4) -o NativeBinding.so \ # (5) NativeBinding.c $ javac -d classes -cp src -h jni q/r/s/NativeBinding.java 💡 Avant le JDK10 il faut utiliser javah. Voir JEP-313
  16. Les technos actuelles : JNI À noter si le code

    utilise plutôt System.loadLibrary("NativeBinding") Alors ils faut indiquer ou chercher cette librairie $ java -Djava.library.path=path/to/jni-lib -cp classes q.r.s.NativeBinding
  17. Les technos actuelles : JNI Pour appeler du code natif,

    la JVM doit effectuer quelques opérations. • Initialiser une stack C valide • Initialiser les registres du CPU (et les remettre en place) • Instructions de barrières de mémoire (fencing) sur le CPU • Empêche certaines optimizations comme l'inlining • JNI permet de définir des sections critiques (Get*Critical) empêchant l’action du GC (au minimum sur une région) • Échanger des données (objets, tableaux) peut demander de la copie et/ou de la sérialisation
  18. Les technos actuelles : JNA • Premier fragment montré dès

    1999 et 1ère release en 2006 • Windows uniquement au début • Inventé par la team ayant bossé sur JNI • Brique omniprésente • Souvent le choix retenu pour interagir avec les librairies natives
  19. Les technos actuelles : JNA JNA est facile à utiliser

    Il n’y a plus qu’à utiliser l’API public interface JNA_Library extends Library { JNA_Library INSTANCE = (JNA_Library) Native.loadLibrary( "c", JNA_Library.class ); // (1) boolean isatty(int fileDescriptor); // (2) } JNA_Library.INSTANCE.isattty( 0);
  20. Les technos actuelles : JNA • Support de types natif

    (wstring, structure, union, etc.) • Beaucoup de reflection pour le ease-of-use • Gère la sérialization, segments mémoire • Repose tout de même sur JNI (invisible) • Dispatch par la library native de JNA basé sur libffi libffi applique des routines écrites en assembleur à la main pour simuler un callsite C
  21. Les technos actuelles : JNR-FFI JNR-FFI est un projet open

    source débuté en 2011. Son but : être aussi simple que JNA, avec les performances en plus. The Java Native Runtime Project https://github.com/jnr public interface IsATTY_JNRFFI { boolean isatty(int fileDescriptor); // (1) } import jnr.ffi.LibraryLoader; public class JnrffiIsATTY { public static void main(String[] args) { IsATTY_JNRFFI c = LibraryLoader.create(IsATTY_JNRFFI.class).load( "c"); System.out.printf("stdin : %s%n", c.isatty(0)); System.out.printf("stdout: %s%n", c.isatty(1)); System.out.printf("stderr: %s%n", c.isatty(3)); } }
  22. Les technos actuelles : JNR-FFI • Support de types natif

    (wstring, structure, union, etc.) • Gestion des classloaders • Génère du bytecode plutôt que de faire la reflection → nettement plus performant • Tire parti également de libffi JNR-FFI est le projet qui inspirera le Projet Panama en 2014 • JEP-191 Foreign Function Interface Initité notamment par Charles Nutter de JRuby
  23. Projet Panama : Objectif monde natif Préserver la culture Java

    Opportunité : Tirer profit des MethodHandles Attention aux différences de : • Syntaxe, sémantique • Nommage, namespace • Types de données • Gestion de la mémoire • Exceptions • Performance • Sécurité
  24. Projet Panama Plus facile, plus sécurisé, plus rapide ➔ Support

    des Fonctions étrangères et des types de données étrangers (FFI) ➔ Flexible dans le transfert de donnée ➔ Support de types natif moderne, comme les vecteurs Initité notamment par John Rose (bosse sur le JVM depuis la version 1, participe à beaucoup de sous système : sécurité, Unsafe, indy, valhala, java-on-java, …)
  25. JEP-412 Foreign Function & Memory API JEP-191 Foreign Function Interface

    JEP-338 Vector API (Incubator) JEP-370 Foreign Memory Access API (Incubator) JEP-383 Foreign Memory Access API (2nd Incubator) JEP-389 Foreign Linker API (Incubator) JEP-393 Foreign Memory Access API (3rd Incubator) JEP-414 Vector API (2nd Incubator) JEP-417 Vector API (3nd Incubator)
  26. • Le projet panama, c’est quoi ? • Appeler du

    code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  27. Code natif : Appels simples

  28. libsodium Bibliothèque cryptographique (chiffrement, signatures, hashing, etc.) Écrite en C,

    multi-plateforme (OS, architectures) Usage facile et rapide
  29. Étape 0 : Configuration JDK 17

  30. Étape 0 : Configuration JDK 17 Incubation ⇒ le code

    est livré dans un module, à activer manuellement $ javac --add-modules jdk.incubator.foreign ... $ java --add-modules jdk.incubator.foreign ...
  31. Étape 0 : Configuration JDK 17 Incubation ⇒ le code

    est livré dans un module, à activer manuellement Pour exécuter des appels natif il faut ajouter $ javac --add-modules jdk.incubator.foreign ... $ java --add-modules jdk.incubator.foreign ... $ java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign ... Toujours en discussion
  32. Étape 0 : Configuration Faisable avec module-info.java module jmh.panama {

    requires jdk.incubator.foreign; // ... }
  33. Étape 0 : Configuration Faisable avec module-info.java module jmh.panama {

    requires jdk.incubator.foreign; // ... } Module path… Toute les dépendances ne sont pas prêtes
  34. Étape 1: Charger la bibliothèque 1. Utiliser une fonction système

    (eg de la libc, etc.) CLinker.systemLookup()
  35. Étape 1: Charger la bibliothèque 1. Utiliser une fonction système

    (eg de la libc, etc.) CLinker.systemLookup() .lookup("getpid").get() → Addresse mémoire de la fonction (pour ce process)
  36. Étape 1: Charger la bibliothèque System.load / System.loadLibrary monte la

    bibliothèque dans un segment mémoire Mémoire virtuelle du processus Java heap Stacks java Autres
  37. Étape 1: Charger la bibliothèque System.load / System.loadLibrary monte la

    bibliothèque dans un segment mémoire Mémoire virtuelle du processus Java heap Stacks java Autres libsodium Le mécanisme de lookup va rechercher les symboles C dans ce(s) segment(s).
  38. Étape 1: Charger la bibliothèque 1. Utiliser une fonction système

    (eg de la libc, etc.) 2. Charger une lib utilisateur a. En passant un chemin CLinker.systemLookup().lookup( "getpid").get() System.load("/usr/local/lib/libsodium.dylib "); SymbolLookup.loaderLookup().lookup( "crypto_box_sealbytes ").get(); 💡 lookup() retourne une adresse mémoire de la fonction native (pour ce process)
  39. Étape 1: Charger la bibliothèque 1. Utiliser une fonction système

    (eg de la libc, etc.) 2. Charger une lib utilisateur a. En passant un chemin b. Avec le nom de la bibliothèque (Doit être présente dans un des dossiers de java.library.path) CLinker.systemLookup().lookup( "getpid").get() System.loadLibrary("sodium"); SymbolLookup.loaderLookup().lookup( "crypto_box_sealbytes ").get(); System.load("/usr/local/lib/libsodium.dylib "); SymbolLookup.loaderLookup().lookup( "crypto_box_sealbytes ").get(); 💡 lookup() retourne une adresse mémoire de la fonction native (pour ce process)
  40. Ce qu’il se passe lors d’un downcallHandle *.lookup("getpid").get() ⇒ addresse

    lib Adresse de getpid
  41. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) *.lookup("getpid").get() ⇒ addresse lib Adresse de getpid
  42. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) *.lookup("getpid").get() ⇒ addresse Création de code exécutant la méthode native Code cache lib Adresse de getpid
  43. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) Calcul de la taille de la stack native Arg 1 Arg 2 Arg 2 Stack 0x00CAFE *.lookup("getpid").get() ⇒ addresse Création de code exécutant la méthode native Code cache lib Adresse de getpid
  44. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) Calcul de la taille de la stack native Copie des valeurs primitives dans la stack native Arg 1 Arg 2 Arg 2 Stack 0x00CAFE Java heap *.lookup("getpid").get() ⇒ addresse Création de code exécutant la méthode native Code cache lib Adresse de getpid
  45. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) Calcul de la taille de la stack native Copie des valeurs primitives dans la stack native Arg 1 Arg 2 Arg 2 Stack 0x00CAFE Java heap Invocation native via JNI *.lookup("getpid").get() ⇒ addresse Création de code exécutant la méthode native Code cache lib Adresse de getpid
  46. Ce qu’il se passe lors d’un downcallHandle CLinker.getInstance() Linker spécifique

    pour OS, Architecture (amd64|x86_64|aarch64) Calcul de la taille de la stack native Copie des valeurs primitives dans la stack native Arg 1 Arg 2 Arg 2 Stack 0x00CAFE Ret Stack 0x00CAFE Copie des valeurs primitives dans la stack native Java heap Java heap Invocation native via JNI *.lookup("getpid").get() ⇒ addresse Création de code exécutant la méthode native Code cache lib Adresse de getpid
  47. Panama tire parti des MethodHandle pour appeler le code natif

    Dès Java 8, à la place de l’API de reflection Étape 2: Appel de méthode sans paramètre A method handle is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values. These transformations are quite general, and include such patterns as conversion, insertion, deletion, and substitution. JDK 1.7 MethodHandle mh = MethodHandles.lookup().findVirtual( TargetClass.class, "targetMetbod", MethodType.methodType(void.class, String.class) ); mh.invokeExact(targetInstance, "...");
  48. Étape 2: Appel de méthode sans paramètre La génération de

    MethodHandle passe par CLinker Invocation classique de la référence (vers la méthode native) CLinker.getInstance().downcallHandle( methodAddress, // (1) MethodType.methodType( int.class), // (2) FunctionDescriptor.of(CLinker.C_INT) // (3) ); int result = (int) crypto_box_sealbytes.invokeExact();
  49. Étape 3: Appel de méthode avec paramètres

  50. Étape 3: Appel de méthode avec paramètres Pour passer des

    arguments vers le monde natif, il faut les copier en dehors de la Heap Java. Mémoire virtuelle du processus libsodium segment off-heap Java heap
  51. Étape 3: Appel de méthode avec paramètres Pour passer des

    arguments vers le monde natif, il faut les copier en dehors de la Heap Java. Au retour de la méthode, il faut copier le résultat dans la Java Heap Mémoire virtuelle du processus libsodium segment off-heap Java heap
  52. Étape 3: Appel de méthode avec paramètres Exemple avec crypto_box_keypair

    Cette méthode prend deux pointeurs • vers une zone mémoire off-heap • le consommateur à la responsabilité d’initialiser ces zones unsigned char recipient_pk[crypto_box_PUBLICKEYBYTES]; unsigned char recipient_sk[crypto_box_SECRETKEYBYTES]; crypto_box_keypair( recipient_pk, recipient_sk);
  53. Étape 3: Appel de méthode avec paramètres MethodHandle crypto_box_keypair =

    CLinker.getInstance().downcallHandle( libsodiumLookup.lookup( "crypto_box_keypair" ).get(), MethodType.methodType( void.class, MemoryAddress.class, // pk MemoryAddress.class // sk ), FunctionDescriptor.ofVoid(C_POINTER, C_POINTER) ); Représente les pointeurs vers pk et sk
  54. Étape 3: Appel de méthode avec paramètres MethodHandle crypto_box_keypair =

    CLinker.getInstance().downcallHandle( libsodiumLookup.lookup( "crypto_box_keypair" ).get(), MethodType.methodType( void.class, MemoryAddress.class, // pk MemoryAddress.class // sk ), FunctionDescriptor.ofVoid(C_POINTER, C_POINTER) ); var recipientPublicKey = MemorySegment.allocateNative(crypto_box_publickeybytes(), scope); var recipientSecretKey = MemorySegment.allocateNative(crypto_box_secretkeybytes(), scope); crypto_box_keypair.invokeExact( recipientPublicKey.address(), recipientSecretKey.address());
  55. Étape 3: Appel de méthode avec paramètres MethodHandle crypto_box_keypair =

    CLinker.getInstance().downcallHandle( libsodiumLookup.lookup( "crypto_box_keypair" ).get(), MethodType.methodType( void.class, MemoryAddress.class, // pk MemoryAddress.class // sk ), FunctionDescriptor.ofVoid(C_POINTER, C_POINTER) ); try (var scope = ResourceScope.newConfinedScope()) { var recipientPublicKey = MemorySegment.allocateNative(crypto_box_publickeybytes(), scope); var recipientSecretKey = MemorySegment.allocateNative(crypto_box_secretkeybytes(), scope); crypto_box_keypair.invokeExact(recipientPublicKey.address(), recipientSecretKey.address()); return new CryptoBoxKeyPair( recipientPublicKey.toByteArray(), recipientSecretKey.toByteArray() ); }
  56. Scope et allocation ResourceScope : Aide à la gestion du

    cycle de vie des segments mémoire Le scope est passé à la création de la ressource, eg. MemorySegment::allocateNative • globalScope ⇒ scope implicite, pas de gestion automatique RSS
  57. Scope et allocation ResourceScope : Aide à la gestion du

    cycle de vie des segments mémoire Le scope est passé à la création de la ressource, eg. MemorySegment::allocateNative • globalScope ⇒ scope implicite, pas de gestion automatique Pour une gestion explicite et déterministe (comprendre libère les segments à la sortie du try-with-resource) • newConfinedScope ⇒ implique un restriction au thread en cours • newSharedScope ⇒ pas de thread propriétaire RSS
  58. Scope et allocation MemorySegment::allocateNative ⇒ effectue un malloc (allocateur système)

  59. Scope et allocation MemorySegment::allocateNative ⇒ malloc (allocateur système) Stratégie d’allocation

    de la mémoire avec SegmentAllocator Interface avec 3 implémentations par défaut
  60. Scope et allocation MemorySegment::allocateNative ⇒ malloc (allocateur système) Stratégie d’allocation

    de la mémoire avec SegmentAllocator Interface avec 3 implémentations par défaut • ofScope ⇒ effectue un malloc
  61. Scope et allocation MemorySegment::allocateNative ⇒ malloc (allocateur système) Stratégie d’allocation

    de la mémoire avec SegmentAllocator Interface avec 3 implémentations par défaut • ofScope ⇒ effectue un malloc • ofSegment ⇒ recycle le même segment
  62. Scope et allocation MemorySegment::allocateNative ⇒ malloc (allocateur système) Stratégie d’allocation

    de la mémoire avec SegmentAllocator Interface avec 3 implémentations par défaut • ofScope ⇒ effectue un malloc • ofSegment ⇒ recycle le même segment • arenaAllocator ⇒ Gestion de la mémoire par arène (région) new freed
  63. Étape 3: Appel de méthode avec paramètres MethodHandle crypto_box_keypair =

    CLinker.getInstance().downcallHandle( libsodiumLookup.lookup( "crypto_box_keypair" ).get(), MethodType.methodType( void.class, MemoryAddress.class, // pk MemoryAddress.class // sk ), FunctionDescriptor.ofVoid(C_POINTER, C_POINTER) ); try (var scope = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var recipientPublicKey = allocator.allocate(crypto_box_publickeybytes()); var recipientSecretKey = allocator.allocate(crypto_box_secretkeybytes()); crypto_box_keypair.invokeExact(recipientPublicKey.address(), recipientSecretKey.address()); return new CryptoBoxKeyPair( recipientPublicKey.toByteArray(), recipientSecretKey.toByteArray() ); }
  64. Étape 4: Appel de méthode avec paramètres Exemple avec crypto_box_seal

    Cette méthode • prend trois pointeurs ◦ le premier argument est le tableau ou sera stocké le message chiffré • retourne un entier avec un status int crypto_box_seal( unsigned char *c, const unsigned char *m, unsigned long long mlen, const unsigned char *pk)
  65. Étape 4: Appel de méthode avec paramètres var crypto_box_seal =

    CLinker.getInstance().downcallHandle( libsodiumLookup.lookup( "crypto_box_seal ").get(), MethodType.methodType( int.class, MemoryAddress.class, // cipherText, output buffer MemoryAddress.class, // message long.class, // message length MemoryAddress.class // publicKey ), FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER, C_LONG_LONG, C_POINTER) );
  66. Étape 4: Appel de méthode avec paramètres try (var scope

    = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var nativeMessage = CLinker.toCString(message, scope); var cipherText = allocator.allocate(crypto_box_sealbytes() + nativeMessage.byteSize()); var ret = (int) crypto_box_seal.invokeExact( cipherText.address(), nativeMessage.address(), (long) nativeMessage.byteSize(), allocator.allocateArray(C_CHAR, publicKey).address()); return cipherText.toByteArray(); }
  67. Étape 4: Appel de méthode avec paramètres try (var scope

    = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var nativeMessage = CLinker.toCString(message, scope); var cipherText = allocator.allocate(crypto_box_sealbytes() + nativeMessage.byteSize()); var ret = (int) crypto_box_seal.invokeExact( cipherText.address(), nativeMessage.address(), (long) nativeMessage.byteSize(), allocator.allocateArray(C_CHAR, publicKey).address()); return cipherText.toByteArray(); }
  68. Étape 4: Appel de méthode avec paramètres try (var scope

    = ResourceScope.newConfinedScope()) { var allocator = SegmentAllocator.ofScope(scope); var nativeMessage = CLinker.toCString(message, scope); var cipherText = allocator.allocate(crypto_box_sealbytes() + nativeMessage.byteSize()); var ret = (int) crypto_box_seal.invokeExact( cipherText.address(), nativeMessage.address(), (long) nativeMessage.byteSize(), allocator.allocateArray(C_CHAR, publicKey).address()); return cipherText.toByteArray(); }
  69. Les classes essentielles • CLinker ◦ Définitions des types natifs

    char ⇒ C_CHAR, long long ⇒ C_LONG_LONG, pointeur ⇒ C_POINTER, varargs ◦ Factory pour de MethodHandle via downcallHandle ◦ Conversion Java String ⇔ C String • MemorySegment ◦ Représente une zone mémoire off-heap ◦ Méthodes pour associer la disposition (e.g tableau de double) ◦ Peut représenter un fichier memory mappé • MemoryAddress ◦ Représente une adresse dans la mémoire virtuelle du processus.
  70. Les classes essentielles • SegmentAllocator ◦ Primitives de gestion de

    la mémoire • ResourceScope ◦ Supporte le périmètre et le cycle de vie de resources. ◦ Imbricable Pour aller plus loin • MemoryLayout ◦ Outillage pour définir la disposition d’un segment de mémoire
  71. Ce que je souhaite aborder ce soir • Le projet

    panama, c’est quoi ? • Appeler du code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  72. jextract jextract \ -d src/main/java \ -l sodium \ --source

    \ --target-package com.github.bric3.sodium \ -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ \ -I $(brew --prefix)/include/sodium \ @sodium.conf \ $(brew --prefix)/include/sodium.h jextract @sodium-only.conf $(brew --prefix)/include/sodium.h jextract \ -d src/main/java \ -l sodium \ --source \ --target-package com.github.bric3.sodium \ -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ \ -I $(brew --prefix)/include/sodium \ --dump-includes sodium.conf \ $(brew --prefix)/include/sodium.h Build 17-panama+3-167 https://jdk.java.net/panama/
  73. Ce que je souhaite aborder ce soir • Le projet

    panama, c’est quoi ? • Appeler du code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  74. Demo REMEMBER: The numbers below are just data. To gain

    reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark Mode Cnt Score Error Units CryptoBoxSealBenchmark.jna avgt 50 160605.022 ± 1142.872 ns/op CryptoBoxSealBenchmark.jni avgt 50 150353.548 ± 750.460 ns/op CryptoBoxSealBenchmark.jnr avgt 50 213407.139 ± 26843.546 ns/op CryptoBoxSealBenchmark.panama avgt 50 151025.638 ± 637.727 ns/op
  75. Demo Benchmark Mode Cnt Score Error Units CryptoBoxSealBenchmark.jna avgt 50

    160197.866 ± 1009.495 ns/op CryptoBoxSealBenchmark.jni avgt 50 151259.674 ± 1446.900 ns/op CryptoBoxSealBenchmark.jnr avgt 50 179482.479 ± 1735.433 ns/op CryptoBoxSealBenchmark.panama avgt 50 152633.815 ± 1247.167 ns/op CryptoBoxSealBenchmark.panama_off_heap avgt 50 151091.119 ± 917.583 ns/op
  76. Ce que je souhaite aborder ce soir • Le projet

    panama, c’est quoi ? • Appeler du code natif ◦ Appels simple ◦ Utiliser une API étrangère • L’outil jextract • Benchmark • Pour finir
  77. Pour finir

  78. À voir aussi Appels natifs vers Java : upcallStub Memory

    mapping de fichiers MemoryAddress jm = CLinker.getInstance().upcallStub( MethodHandle, // the handle to the Java method FunctionDescriptor, // equivalent C signature ResourceScope // scope ); c_method.invokeExact( ..., jm.address() // pointeur vers la méthode Java ); MemorySegment.mapFile(path, // chemin du fichier 0, // offset Files.size(path), // taille du mapping FileChannel.MapMode.READ_ONLY, // mode scope);
  79. À noter Changements à venir dans un prochain incubateur •

    Ajustements autour des APIs de gestion de ressources, pooling, keep alive, … • Outillage en cours de finalisation Une fois finalisée, l’API sera intégrée dans les modules par défaut, e.g. possible ajustements de signature, voire déplacement de méthode.
  80. À noter Support de différents OS / architectures. Alignement…, etc.

    Support Android : Pas pour tout de suite • Android SDK != JDK • Déjà compliqué d’avoir un bon support de Java 8 (API Level 26 ajoute java.time, java.nio.file et java.lang.invoke)
  81. Questions ? https://inside.java/ https://mail.openjdk.java.net/pipermail/panama-dev/ https://jdk.java.net/panama/ https://blog.arkey.fr/ @BriceDutheil