2
votes

La concaténation de chaînes lors de l'affectation est-elle efficace?

Je sais que l'utilisation de l'opérateur de concaténation "+" pour créer des chaînes est très inefficace, et c'est pourquoi il est recommandé d'utiliser la classe StringBuilder, mais je me demandais si ce type de modèle est également inefficace?

String some = a + "\t" + b + "\t" + c + "\t" + d + "\t" + e;

Je suppose qu'ici, le compilateur optimisera bien l'affectation, ou pas?


12 commentaires

C'est très bien. Le compilateur peut l'optimiser. Voir aussi Comment la concaténation de chaînes est-elle implémentée dans Java 9?


Je serais prudent en disant que @khelwood, nous ne savons pas que a / b / etc sont toutes des constantes (c'est là où il s'optimiserait en un littéral). Sinon, la seule optimisation que nous pourrions voir ici est le compilateur qui échange littéralement la concaténation avec StringBuilder tout seul. Dans une boucle, cela pourrait être beaucoup de constructeurs de chaînes inutiles qui sont résolus.


Cela dépend si a, b, c, d sont des constantes .... le compilateur l'optimisera s'il peut résoudre les valeurs (au moment de la compilation)


Pourquoi doivent-ils être des constantes? Pourquoi y aurait-il un problème avec les variables normales? StringBuilder fonctionnerait également très bien avec les variables, non. Une autre optimisation peut ne pas fonctionner, mais de quel type d'optimisation parlons-nous alors?


L'optimisation à laquelle il pense est celle où le compilateur remplacera une concaténation entre des constantes par juste la valeur littérale. Il existe également d'autres optimisations.


@Rogue Je ne comprends pas quel problème vous décrivez. Si la question est "Puis-je écrire une déclaration de chaîne sur une ligne au lieu d'utiliser explicitement un StringBuilder et compter sur le compilateur pour comprendre comment l'exécuter?" et vous dites que le compilateur pourrait avoir à se rabattre sur l'utilisation d'un StringBuilder, alors le code va bien. Quelle est l'alternative?


@khelwood dans le cas d'une boucle, en construisant le StringBuilder en dehors de la boucle et en y ajoutant sans en créer une chaîne à chaque itération. C'est plus une question de portée à ce stade, mais je pense qu'il y a un doublon ici pour une question SO sur les optimisations de concat en java. Je ne l'ai toujours pas encore trouvé


@Rogue Dans autres situations , un StringBuilder explicite est plus rapide. Mais le code de la question ne s'ajoute pas à une chaîne dans une boucle. C'est rassembler un tas de trucs en une seule ligne.


Oui, StringBuilder est une sorte d'optimisation. Mais les expressions constantes peuvent être entièrement optimisées à une valeur littérale. Cela dépend donc du type d'optimisation auquel vous pensez.


Bien sûr, avec juste cette ligne singulière seule, c'est juste un cas à considérer (surtout si quelqu'un est tombé sur cette page de Google). Par exemple. si cette ligne était dans une boucle.


"optimiser à un littéral" n'était pas dans l'énoncé du problème. Le compilateur optimisera la concaténation de chaînes aux appels StringBuilder.append dans une seule expression impliquant des termes non constants. Comme indiqué, ce ne sera pas le cas avec des concaténations sur différentes lignes, bien que le compilateur d'exécution JIT / HotSpot puisse faire quelque chose.


Pouvez-vous expliquer cela davantage? Les explications aident les autres à apprendre de votre réponse


3 Réponses :


5
votes

Cet exemple particulier sera intégré par le compilateur:

Code:
  stack=1, locals=5, args_size=1
     0: ldc           #2                  // String a
     2: astore_1
     3: ldc           #3                  // String bb
     5: astore_2
     6: ldc           #4                  // String ccc
     8: astore_3
     9: ldc           #5                  // String a\tbb\tccc
    11: astore        4
    13: return

Java 9+ intégrera ceci en utilisant invokedynamic avec makeConcatWithConstants le rendant efficace. Comme pour la sortie javap -v :

final String a = "a";
final String b = "bb";
final String c = "ccc";
String some = a + "\t" + b + "\t" + c;

Cependant si les a b et c sont des constantes de temps de compilation, le compilateur optimisera davantage le code:

Code:
  stack=3, locals=5, args_size=1
     0: ldc           #2                  // String a
     2: astore_1
     3: ldc           #3                  // String bb
     5: astore_2
     6: ldc           #4                  // String ccc
     8: astore_3
     9: aload_1
    10: aload_2
    11: aload_3
    12: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    17: astore        4
    19: return

et certains seront chargés avec une valeur constante:

String a = "a";
String b = "bb";
String c = "ccc";
String some = a + "\t" + b + "\t" + c;

Dans d'autres circonstances, par exemple pour boucle, le compilateur peut ne pas être en mesure de produire du code optimisé, donc StringBuilder pourrait être plus rapide.


3 commentaires

Je voulais simplement insister sur le fait que cela ne s'applique qu'à la concaténation littérale / constante. Si a / etc est une variable qui a été, par exemple, transmise, nous perdons cette optimisation (et elle sera probablement simplement remplacée par un appel StringBuilder , d'après ce que je 'ai vu dans le passé).


@KarolDowbecki Je n'ai aucune idée de comment cela répond à la question. StringConcatFactory n'a que deux méthodes: makeConcat et makeConcatWithConstants . Maintenant, ce que ces méthodes utiliseront réellement à l'exécution est une toute autre histoire.


@Rogue que diriez-vous d'essayer de passer a comme argument à une méthode et de décompiler cela? Ce sera toujours makeConcatWithConstants ...



2
votes

En général, la concaténation de chaînes avec + et en utilisant StringBuilder est absolument correcte et fonctionne. Mais dans différentes situations, la concaténation avec + devient moins efficace que l'utilisation de StringBuilder .

La concaténation de chaînes PAS EN BOUCLE - EFFICACE !!!

Ceci fait de bonnes performances, car JVM transforme cela en utilisant StringBuilder.

String some = "";

for (int i = 0; i < 10; i++) {
    some = new StringBuilder(some).append(a).append('\t').append(c).append('\t')
                               .append(d).append('\t').append(e).append('t');
}

C'est OK, car JVM change en interne ce code pour le suivant: p>

StringBuilder buf = new StringBuilder();

for (int i = 0; i < 10; i++)
    buf.append(a).append('\t').append(c).append('\t')
       .append(d).append('\t').append(e).append('t');

String some = buf.toString();

PS StringBuilder a un tampon interne char [] . Si vous savez combien de temps durera la chaîne de résultat, il est préférable de réserver tout le tampon au début. Par exemple. dans le cas où la chaîne finale sera au plus 1024 caractères , alors vous pouvez faire new StringBuilder (1024)

Concaténation de chaîne EN BOUCLE - PAS EFFICACE! !!

Cela entraîne de mauvaises performances, car la JVM ne peut pas envelopper la boucle while avec un StringBuilder , comme ceci:

String some = new StringBuilder().append(a).append('\t').append(c).append('\t')
                                 .append(d).append('\t').append(e).toString();

mais JVM toujours capable d'optimiser toutes les concaténations dans chaque itération de boucle; comme ceci:

String some = a + "\t" + b + "\t" + c + "\t" + d + "\t" + e;

Comme vous pouvez le voir, il y a quelques inconvénients à utiliser la concaténation de chaînes en boucle.


0 commentaires

6
votes

Votre prémisse "que l'utilisation de l'opérateur de concaténation" + "pour construire des chaînes est très inefficace", n'est pas correcte. Premièrement, la concaténation de chaînes en elle-même n'est pas une opération bon marché, car elle implique la création d'une nouvelle chaîne contenant toutes les chaînes concaténées, d'où la nécessité de copier le contenu des caractères. Mais cela s'applique toujours, quelle que soit comment vous le faites.

Lorsque vous utilisez l'opérateur + , vous dites ce que vous voulez faire, sans dire comment le faire. Même la spécification du langage Java n'exige pas de stratégie d'implémentation particulière, sauf que la concaténation des constantes au moment de la compilation doit être effectuée au moment de la compilation. Donc, pour les constantes à la compilation, l'opérateur + est la solution la plus efficace¹.

En pratique, tous les compilateurs couramment utilisés de Java 5 à Java 8 génèrent du code en utilisant un StringBuilder sous le capot (avant Java 5, ils utilisaient StringBuffer ). Cela s'applique à des déclarations comme la vôtre, donc le remplacer par une utilisation manuelle de StringBuilder ne gagnerait pas beaucoup. Vous pourriez être légèrement meilleur que le code généré par un compilateur typique en fournissant une capacité initiale raisonnable, mais c'est tout.

À partir de Java 9, les compilateurs génèrent une instruction invokedynamic qui permet au moteur d'exécution de fournir le code réel effectuant la concaténation. Cela pourrait être un code StringBuilder similaire à celui utilisé dans le passé, mais aussi quelque chose de complètement différent. Plus particulièrement, le code fourni par le runtime peut accéder à des fonctionnalités spécifiques à l'implémentation, ce que le code de l'application ne peut pas. Alors maintenant, la concaténation de chaînes via + peut être encore plus rapide que le code basé sur StringBuilder .

Comme cela ne s'applique qu'à une seule expression de concaténation, lors de l'exécution d'une construction de chaîne à l'aide de plusieurs instructions ou même d'une boucle, l'utilisation cohérente d'un StringBuilder pendant toute la construction peut être plus rapide que les opérations de concaténation multiples. Cependant, comme le code s'exécute dans un environnement d'optimisation, avec une JVM reconnaissant certains de ces modèles, cela ne peut même pas être dit avec certitude.

C'est le moment de se souvenir de l'ancienne règle, d'essayer d'optimiser les performances uniquement, lorsqu'il y a un problème réel avec les performances. Et vérifiez toujours avec des outils de mesure impartiaux si une tentative d'optimisation améliore vraiment les performances. Il existe de nombreux mythes répandus, faux ou dépassés, sur les astuces d'optimisation des performances.

¹ sauf que vous avez des parties répétées et que vous souhaitez réduire la taille du fichier de classe


1 commentaires

Je vous remercie! Je n'ai pas eu le temps de poster une réponse car je n'aime vraiment pas la réponse acceptée et je ne sais pas non plus comment dire qu'au niveau du bytecode, il y a un StringConcatFactory :: makeConcatWithConstants en fait y répond.