Nous devons constamment créer des chaînes pour la sortie du journal, etc. Au fil des versions de JDK, nous avons appris quand utiliser StringBuffer
(plusieurs ajouts, thread-safe) et StringBuilder
(plusieurs ajouts, non-thread-safe).
Quels sont les conseils d'utilisation String.format()
? Est-il efficace, ou sommes-nous obligés de nous en tenir à la concaténation pour les lignes simples où les performances sont importantes?
par exemple, vieux style laid,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
vs nouveau style bien rangé (String.format, qui est peut-être plus lent),
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
Remarque: mon cas d'utilisation spécifique est les centaines de chaînes de journaux `` à une ligne '' dans tout mon code. Ils n'impliquent pas de boucle, c'est donc StringBuilder
trop lourd. Je m'intéresse String.format()
spécifiquement.
Réponses:
J'ai écrit une petite classe pour tester ce qui a les meilleures performances des deux et + vient avant le format. par un facteur de 5 à 6. Essayez-le vous-même
L'exécution de ce qui précède pour différents N montre que les deux se comportent linéairement, mais
String.format
sont 5 à 30 fois plus lents.La raison en est que dans l'implémentation actuelle,
String.format
commence par analyser l'entrée avec des expressions régulières, puis remplit les paramètres. La concaténation avec plus, en revanche, est optimisée par javac (pas par le JIT) et utiliseStringBuilder.append
directement.la source
J'ai pris du code Hhafez et ajouté un test de mémoire :
J'exécute ceci séparément pour chaque approche, l'opérateur '+', String.format et StringBuilder (appelant toString ()), de sorte que la mémoire utilisée ne sera pas affectée par d'autres approches. J'ai ajouté plus de concaténations, faisant de la chaîne "Blah" + i + "Blah" + i + "Blah" + i + "Blah".
Les résultats sont les suivants (moyenne de 5 exécutions chacun):
Temps d'approche (ms) Mémoire allouée (longue)
Opérateur '+' 747 320
504 String.format 16484 373 312
StringBuilder 769 57 344
Nous pouvons voir que String '+' et StringBuilder sont pratiquement identiques dans le temps, mais StringBuilder est beaucoup plus efficace dans l'utilisation de la mémoire. Ceci est très important lorsque nous avons de nombreux appels de journal (ou toute autre instruction impliquant des chaînes) dans un intervalle de temps suffisamment court pour que le garbage collector ne puisse pas nettoyer les nombreuses instances de chaîne résultant de l'opérateur '+'.
Et une note, BTW, n'oubliez pas de vérifier le niveau de journalisation avant de construire le message.
Conclusions:
la source
+
opérateur compile leStringBuilder
code équivalent . Les micro-benchmarks comme celui-ci ne sont pas un bon moyen de mesurer les performances - pourquoi ne pas utiliser jvisualvm, c'est dans le jdk pour une raison.String.format()
sera plus lent, mais en raison du temps nécessaire pour analyser la chaîne de format plutôt que des allocations d'objets. Le report de la création d'artefacts de journalisation jusqu'à ce que vous soyez sûr qu'ils sont nécessaires est un bon conseil, mais s'il a un impact sur les performances, il est au mauvais endroit.And a note, BTW, don't forget to check the logging level before constructing the message.
n'est pas un bon conseil. En supposant que nous parlonsjava.util.logging.*
spécifiquement, la vérification du niveau de journalisation consiste à effectuer un traitement avancé qui entraînerait des effets néfastes sur un programme dont vous ne voudriez pas lorsqu'un programme n'a pas la journalisation activée au niveau approprié. Le formatage des chaînes n'est pas du tout ce type de traitement. Le formatage fait partie de lajava.util.logging
structure et l'enregistreur lui-même vérifie le niveau d'enregistrement avant que le formateur ne soit jamais appelé.Tous les benchmarks présentés ici ont des défauts , donc les résultats ne sont pas fiables.
J'ai été surpris que personne n'utilise JMH pour l'analyse comparative, alors je l'ai fait.
Résultats:
Les unités sont des opérations par seconde, plus c'est mieux. Code source de référence . OpenJDK IcedTea 2.5.4 Java Virtual Machine a été utilisé.
Ainsi, l'ancien style (en utilisant +) est beaucoup plus rapide.
la source
Votre ancien style laid est automatiquement compilé par JAVAC 1.6 comme:
Il n'y a donc absolument aucune différence entre cela et l'utilisation d'un StringBuilder.
String.format est beaucoup plus lourd car il crée un nouveau formateur, analyse votre chaîne de format d'entrée, crée un StringBuilder, y ajoute tout et appelle toString ().
la source
+
et enStringBuilder
effet. Malheureusement, il y a beaucoup de désinformation dans d'autres réponses de ce fil. Je suis presque tenté de changer la question enhow should I not be measuring performance
.Le String.format de Java fonctionne comme ceci:
si la destination finale de ces données est un flux (par exemple, le rendu d'une page Web ou l'écriture dans un fichier), vous pouvez assembler les morceaux de format directement dans votre flux:
Je suppose que l'optimiseur optimisera le traitement des chaînes de format. Si tel est le cas, vous vous retrouvez avec des performances amorties équivalentes pour dérouler manuellement votre String.format dans un StringBuilder.
la source
String.format
dans des boucles internes (exécutées des millions de fois) entraînait plus de 10% de mon temps d'exécutionjava.util.Formatter.parse(String)
. Cela semble indiquer que dans les boucles internes, vous devez éviter d'appelerFormatter.format
ou tout ce qui l'appelle, y comprisPrintStream.format
(une faille dans la bibliothèque standard de Java, IMO, d'autant plus que vous ne pouvez pas mettre en cache la chaîne de format analysée).Pour développer / corriger la première réponse ci-dessus, ce n'est pas la traduction que String.format aiderait en fait.
String.format vous aidera à imprimer une date / heure (ou un format numérique, etc.), où il y a des différences de localisation (l10n) (c'est-à-dire que certains pays imprimeront 04Feb2009 et d'autres imprimeront Feb042009).
Avec la traduction, vous parlez simplement de déplacer toutes les chaînes externalisables (comme les messages d'erreur et autres) dans un ensemble de propriétés afin que vous puissiez utiliser le bon ensemble pour la bonne langue, en utilisant ResourceBundle et MessageFormat.
En regardant tout ce qui précède, je dirais que, en termes de performances, la concaténation String.format vs plain se résume à ce que vous préférez. Si vous préférez regarder les appels vers .format plutôt que la concaténation, alors allez-y, allez-y.
Après tout, le code est lu beaucoup plus qu'il n'est écrit.
la source
Dans votre exemple, les performances probalby ne sont pas trop différentes mais il y a d'autres problèmes à considérer: à savoir la fragmentation de la mémoire. Même une opération de concaténation crée une nouvelle chaîne, même si elle est temporaire (il faut du temps pour la GC et c'est plus de travail). String.format () est juste plus lisible et implique moins de fragmentation.
De plus, si vous utilisez beaucoup un format particulier, n'oubliez pas que vous pouvez utiliser directement la classe Formatter () (tout ce que fait String.format () consiste à instancier une instance de Formatter à usage unique).
Aussi, quelque chose d'autre que vous devez savoir: faites attention à utiliser substring (). Par exemple:
Cette grande chaîne est toujours en mémoire car c'est ainsi que fonctionnent les sous-chaînes Java. Une meilleure version est:
ou
Le deuxième formulaire est probablement plus utile si vous faites d'autres choses en même temps.
la source
En règle générale, vous devez utiliser String.Format car il est relativement rapide et prend en charge la mondialisation (en supposant que vous essayez d'écrire quelque chose qui est lu par l'utilisateur). Cela facilite également la mondialisation si vous essayez de traduire une chaîne contre 3 ou plus par instruction (en particulier pour les langues qui ont des structures grammaticales radicalement différentes).
Maintenant, si vous ne prévoyez jamais de traduire quoi que ce soit, alors vous pouvez vous fier à la conversion Java intégrée des opérateurs +
StringBuilder
. Ou utilisezStringBuilder
explicitement Java .la source
Une autre perspective du point de vue de la journalisation uniquement.
Je vois beaucoup de discussions liées à la connexion à ce fil, alors j'ai pensé ajouter mon expérience en réponse. Peut-être que quelqu'un le trouvera utile.
Je suppose que la motivation de la journalisation à l'aide du formateur vient d'éviter la concaténation des chaînes. Fondamentalement, vous ne voulez pas avoir de surcharge de concaténation de chaîne si vous ne voulez pas l'enregistrer.
Vous n'avez pas vraiment besoin de concaténer / formater, sauf si vous voulez vous connecter. Disons que si je définis une méthode comme celle-ci
Dans cette approche, le cancat / formateur n'est pas vraiment appelé du tout si c'est un message de débogage et debugOn = false
Bien qu'il soit toujours préférable d'utiliser StringBuilder au lieu du formateur ici. La principale motivation est d'éviter tout cela.
En même temps, je n'aime pas ajouter un bloc "si" pour chaque instruction de journalisation car
Par conséquent, je préfère créer une classe d'utilitaires de journalisation avec des méthodes comme ci-dessus et l'utiliser partout sans se soucier des performances atteintes et de tout autre problème lié.
la source
Je viens de modifier le test de hhafez pour inclure StringBuilder. StringBuilder est 33 fois plus rapide que String.format utilisant le client jdk 1.6.0_10 sous XP. L'utilisation du commutateur -serveur abaisse le facteur à 20.
Bien que cela puisse sembler drastique, je considère que cela n'est pertinent que dans de rares cas, car les nombres absolus sont assez faibles: 4 s pour 1 million d'appels String.format simples est en quelque sorte correct - tant que je les utilise pour la journalisation ou la comme.
Mise à jour: Comme l'a souligné sjbotha dans les commentaires, le test StringBuilder n'est pas valide, car il manque une finale
.toString()
.Le facteur d'accélération correct de
String.format(.)
àStringBuilder
est 23 sur ma machine (16 avec le-server
commutateur).la source
Voici la version modifiée de l'entrée hhafez. Il comprend une option de création de chaîne.
}
Temps après pour la boucle 391 Temps après pour la boucle 4163 Temps après pour la boucle 227
la source
La réponse à cela dépend beaucoup de la façon dont votre compilateur Java spécifique optimise le bytecode qu'il génère. Les chaînes sont immuables et, théoriquement, chaque opération "+" peut en créer une nouvelle. Mais, votre compilateur optimise presque certainement les étapes intermédiaires de la création de longues chaînes. Il est tout à fait possible que les deux lignes de code ci-dessus génèrent exactement le même bytecode.
La seule véritable façon de le savoir est de tester le code de manière itérative dans votre environnement actuel. Écrivez une application QD qui concatène les chaînes de manière itérative et voyez comment elles s'arrêtent les unes contre les autres.
la source
Envisagez d'utiliser
"hello".concat( "world!" )
un petit nombre de chaînes dans la concaténation. Il pourrait être encore meilleur pour la performance que d'autres approches.Si vous avez plus de 3 chaînes, pensez à utiliser StringBuilder, ou simplement String, selon le compilateur que vous utilisez.
la source