Comme indiqué dans JEP 280: Indiquer la concaténation de chaînes :
Modifiez la séquence statique de bytecode
String
- de concaténation générée parjavac
pour utiliser les appelsinvokedynamic
aux fonctions de la bibliothèque JDK. Cela permettra des optimisations futures de la concaténationString
sans nécessiter de modifications supplémentaires du bytecode émis parjavac
.
Ici, je veux comprendre l’utilisation de invokedynamic
appelle et comment la concaténation de bytecode est différente de invokedynamic
?
La "vieille" manière de générer un tas d'opérations orientées StringBuilder
. Considérez ce programme:
public class Example {
public static void main(String[] args)
{
String result = args[0] + "-" + args[1] + "-" + args[2];
System.out.println(result);
}
}
Si nous compilons cela avec JDK version 8 ou antérieure, puis utilisons javap -c Example
pour voir le bytecode, nous voyons quelque chose comme ceci:
public class Exemple { public Exemple (); Code: 0: aload_0 1: invokespecial # 1 // Méthode Java/lang /Object."<init>":()V 4: retourne Public static void main (Java.lang.String []); Code : 0: nouveau # 2 // classe Java/lang/StringBuilder 3: dup 4: invokespecial # 3 // Méthode Java/lang/StringBuilder. "<Init> ":() V 7: aload_0 8: iconst_0 9: aaload 10: invokevirtual # 4 // Méthode Java/lang/StringBuilder.append: (Ljava/lang/String;) Ljava/lang/StringBuilder; 13: ldc # 5 // String - 15: invokevirtual # 4 // Méthode Java/lang/StringBuilder.append :( Ljava/lang/String;) Ljava/lang/StringBuilder; 18: aload_0 19: iconst_1 20: aaload 2 1: invokevirtual # 4 // Méthode Java/lang/StringBuilder.append: (Ljava/lang/String;) Ljava/lang/StringBuilder; 24: ldc # 5 // String - 26 : invokevirtual # 4 // Méthode Java/lang/StringBuilder.append: (Ljava/lang/String;) Ljava/lang/StringBuilder; 29: aload_0 30: iconst_2 . 31: aaload 32: invokevirtual # 4 // Méthode Java/lang/StringBuilder.append: (Ljava/lang/String;) Ljava/lang/StringBuilder; 35: invokevirtual # 6 // Méthode Java/lang/StringBuilder.toString :() Ljava/lang/String; 38: astore_1 39: getstatic # 7 // Champ Java/lang/System.out: Ljava/io/PrintStream; 42: aload_1 43: invokevirtual # 8 // Méthode Java/io/PrintStream.println: (Ljava/lang/String;) V 46: retourne }
Comme vous pouvez le constater, il crée un StringBuilder
et utilise append
. Ceci est assez célèbre car la capacité par défaut du tampon intégré dans StringBuilder
n’est que de 16 caractères, et il n’existe aucun moyen pour le compilateur pour savoir affecter plus à l'avance, il faut donc réaffecter. C'est aussi un tas d'appels de méthodes. (Notez que la machine virtuelle Java peut parfois détecter et réécrire ces modèles d'appels pour les rendre plus efficaces, cependant.)
Regardons ce que Java 9 génère:
public class Exemple { public Exemple (); Code: 0: aload_0 1: invokespecial # 1 // Méthode Java/lang /Object."<init>":()V 4: retourne Public static void main (Java.lang.String []); Code : 0: aload_0 1: iconst_0 2: aaload 3: aload_0 4: iconst_1 5: aaload 6: aload_0 7: iconst_2 8: aaload 9: invokedynamic # 2, 0 // InvokeDynamic # 0: makeConcatWithConstants: (Ljava/lang/String; Ljava/lang/String; Ljava/lang/String;) Ljava/lang/String; 14: astore_1 15: getstatic # 3 // Champ Java/lang/System.out: Ljava/io/PrintStream; 18: aload_1 19: invokevirtual # 4 // Méthode Java/io/PrintStream.println: (Ljava/lang/String;) V 22: retour }
Oh mon mais c'est plus court. :-) Il fait un appel unique à makeConcatWithConstants
de StringConcatFactory
, ce qui est indiqué dans sa Javadoc:
Méthodes pour faciliter la création de méthodes de concaténation de chaînes, pouvant être utilisées pour concaténer efficacement un nombre connu d'arguments de types connus, éventuellement après adaptation de type et évaluation partielle des arguments. Ces méthodes sont généralement utilisées comme méthodes d'amorçage pour les sites d'appels
invokedynamic
, afin de prendre en charge la concaténation de chaînes . Fonction du Java Langage de programmation.
Avant d'entrer dans les détails de l'implémentation invokedynamic
utilisée pour l'optimisation de la concaténation de chaînes, à mon avis, il est nécessaire de connaître l'arrière-plan Qu'est-ce qui est appelé Invokedynamic et comment l'utiliser?
L'instruction
invokedynamic
simplifie et potentiellement améliore la mise en œuvre de compilateurs et de systèmes d'exécution pour les langages dynamiques sur la JVM. Pour ce faire, il permet à l’implémenteur de langage de définir un comportement de liaison personnalisé avec l’instructioninvokedynamic
qui implique les étapes suivantes.
J'essaierais probablement de vous expliquer toutes les modifications apportées pour la mise en œuvre de l'optimisation de la concaténation de chaînes.
Définition de la méthode Bootstrap : - avec Java9, les méthodes bootstrap pour invokedynamic
sites d'appels, pour prendre en charge la concaténation de chaînes principalement makeConcat
et makeConcatWithConstants
ont été introduits avec le StringConcatFactory
implémentation.
L'utilisation de invokedynamic fournit une alternative pour sélectionner une stratégie de traduction jusqu'au moment de l'exécution. La stratégie de traduction utilisée dans StringConcatFactory
est similaire à la méthode LambdaMetafactory
introduite dans la previous Java version. En outre, l'un des objectifs du PEC mentionné dans la question est de développer davantage ces stratégies.
Spécification d'entrées de pool constantes : - Il s'agit des arguments statiques supplémentaires de l'instruction invokedynamic
autres que (1) MethodHandles.Lookup
objet qui est une fabrique pour la création de méthodes, dans le contexte de l’instruction invokedynamic
, (2) un objet String
, le nom de la méthode mentionné dans le site d’appel dynamique et ( 3) l'objet MethodType
, la signature de type résolue du site d'appels dynamiques.
Il y a déjà des liens lors du couplage du code. Au moment de l'exécution, la méthode bootstrap est exécutée et crée des liens dans le code lui-même effectuant la concaténation. Elle réécrit l'appel invokedynamic
avec un invokestatic
call. Ceci charge la chaîne constante du pool constant. Les arguments statiques de la méthode bootstrap sont exploités) pour transmettre ces constantes et d'autres constantes directement à la méthode bootstrap appeler.
Utilisation de l'instruction invokedynamic : - Ceci offre les facilités nécessaires pour une liaison paresseuse, en fournissant les moyens à bootstrap l'appel). cible une fois, lors de l'appel initial. L'idée d'optimisation concrète est de remplacer l'ensemble StringBuilder.append
_ danse avec un simple invokedynamic
appel à Java.lang.invoke.StringConcatFactory
, qui acceptera les valeurs nécessitant une concaténation.
La proposition Indify String Concatenation indique, à l'aide d'un exemple, l'analyse comparative de l'application avec Java9, où une méthode similaire est partagée par @ T.J. Crowder est compilé et la différence dans le bytecode est assez visible entre les différentes implémentations.
Je vais ajouter un peu de détails ici. La partie principale à obtenir est que la concaténation de chaînes est effectuée par un décision d'exécution, pas de compilation. Cela peut donc changer, ce qui signifie que vous avez compilé votre code une fois contre Java-9 et que cela peut changer l'implémentation sous-jacente comme bon vous semble, sans qu'il soit nécessaire de recompiler.
Et le deuxième point est qu’à l’heure actuelle il y a 6 possible strategies for concatenation of String
:
private enum Strategy {
/**
* Bytecode generator, calling into {@link Java.lang.StringBuilder}.
*/
BC_SB,
/**
* Bytecode generator, calling into {@link Java.lang.StringBuilder};
* but trying to estimate the required storage.
*/
BC_SB_SIZED,
/**
* Bytecode generator, calling into {@link Java.lang.StringBuilder};
* but computing the required storage exactly.
*/
BC_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that in the end calls into {@link Java.lang.StringBuilder}.
* This strategy also tries to estimate the required storage.
*/
MH_SB_SIZED,
/**
* MethodHandle-based generator, that in the end calls into {@link Java.lang.StringBuilder}.
* This strategy also estimate the required storage exactly.
*/
MH_SB_SIZED_EXACT,
/**
* MethodHandle-based generator, that constructs its own byte[] array from
* the arguments. It computes the required storage exactly.
*/
MH_INLINE_SIZED_EXACT
}
Vous pouvez choisir n'importe lequel d'entre eux via un paramètre: -Djava.lang.invoke.stringConcat
. Notez que StringBuilder
est toujours une option.