Il y a ici deux «forces» en tension: performances vs lisibilité.
Abordons d'abord le troisième problème, les longues files d'attente:
System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");
La meilleure façon d'implémenter cela et de garder la lisibilité, est d'utiliser la concaténation de chaînes:
System.out.println("Good morning everyone. I am here today to present you "
+ "with a very, very lengthy sentence in order to prove a "
+ "point about how it looks strange amongst other code.");
La concaténation de chaîne constante se produira au moment de la compilation et n'aura aucun effet sur les performances. Les lignes sont lisibles et vous pouvez simplement continuer.
Maintenant, à propos de:
System.out.println("Good morning.");
System.out.println("Please enter your name");
contre.
System.out.println("Good morning.\nPlease enter your name");
La deuxième option est nettement plus rapide. Je proposerai environ 2 fois plus vite ... pourquoi?
Parce que 90% (avec une large marge d'erreur) du travail n'est pas lié au dumping des caractères dans la sortie, mais est une surcharge nécessaire pour sécuriser la sortie et y écrire.
Synchronisation
System.out
est un PrintStream
. Toutes les implémentations Java que je connais, synchronisent en interne le PrintStream: Voir le code sur GrepCode! .
Qu'est-ce que cela signifie pour votre code?
Cela signifie que chaque fois que vous appelez System.out.println(...)
vous synchronisez votre modèle de mémoire, vous vérifiez et attendez un verrou. Tous les autres threads appelant System.out seront également verrouillés.
Dans les applications à un seul thread, l'impact de System.out.println()
est souvent limité par les performances d'E / S de votre système, à quelle vitesse pouvez-vous écrire dans un fichier. Dans les applications multithread, le verrouillage peut être plus problématique que l'IO.
Flushing
Chaque impression est rincée . Cela provoque l'effacement des tampons et déclenche une écriture de niveau console dans les tampons. La quantité d'effort effectuée ici dépend de la mise en œuvre, mais il est généralement entendu que les performances du vidage ne sont que partiellement liées à la taille du tampon à vider. Il y a une surcharge importante liée au vidage, où les tampons de mémoire sont marqués comme sales, la machine virtuelle effectue des E / S, etc. Le fait d'engager ces frais généraux une fois, au lieu de deux, est une optimisation évidente.
Quelques chiffres
J'ai mis en place le petit test suivant:
public class ConsolePerf {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
benchmark("Warm " + i);
}
benchmark("real");
}
private static void benchmark(String string) {
benchString(string + "short", "This is a short String");
benchString(string + "long", "This is a long String with a number of newlines\n"
+ "in it, that should simulate\n"
+ "printing some long sentences and log\n"
+ "messages.");
}
private static final int REPS = 1000;
private static void benchString(String name, String value) {
long time = System.nanoTime();
for (int i = 0; i < REPS; i++) {
System.out.println(value);
}
double ms = (System.nanoTime() - time) / 1000000.0;
System.err.printf("%s run in%n %12.3fms%n %12.3f lines per ms%n %12.3f chars per ms%n",
name, ms, REPS/ms, REPS * (value.length() + 1) / ms);
}
}
Le code est relativement simple, il imprime à plusieurs reprises une chaîne courte ou longue à afficher. La longue chaîne contient plusieurs nouvelles lignes. Il mesure le temps nécessaire pour imprimer 1000 itérations de chacun.
Si je l'exécute à l'invite de commande unix (Linux) , que je redirige STDOUT
vers /dev/null
et que j'imprime les résultats réels STDERR
, je peux faire ce qui suit:
java -cp . ConsolePerf > /dev/null 2> ../errlog
La sortie (dans errlog) ressemble à:
Warm 0short run in
7.264ms
137.667 lines per ms
3166.345 chars per ms
Warm 0long run in
1.661ms
602.051 lines per ms
74654.317 chars per ms
Warm 1short run in
1.615ms
619.327 lines per ms
14244.511 chars per ms
Warm 1long run in
2.524ms
396.238 lines per ms
49133.487 chars per ms
.......
Warm 99short run in
1.159ms
862.569 lines per ms
19839.079 chars per ms
Warm 99long run in
1.213ms
824.393 lines per ms
102224.706 chars per ms
realshort run in
1.204ms
830.520 lines per ms
19101.959 chars per ms
reallong run in
1.215ms
823.160 lines per ms
102071.811 chars per ms
Qu'est-ce que ça veut dire? Permettez-moi de répéter la dernière «strophe»:
realshort run in
1.204ms
830.520 lines per ms
19101.959 chars per ms
reallong run in
1.215ms
823.160 lines per ms
102071.811 chars per ms
Cela signifie que, à toutes fins utiles, même si la ligne «longue» est environ 5 fois plus longue et contient plusieurs retours à la ligne, la sortie prend à peu près autant de temps que la ligne courte.
Le nombre de caractères par seconde pour le long terme est 5 fois plus élevé et le temps écoulé est à peu près le même .....
En d'autres termes, vos performances varient en fonction du nombre d'imprimantes dont vous disposez, et non de ce qu'elles impriment.
Mise à jour: que se passe-t-il si vous redirigez vers un fichier, au lieu de / dev / null?
realshort run in
2.592ms
385.815 lines per ms
8873.755 chars per ms
reallong run in
2.686ms
372.306 lines per ms
46165.955 chars per ms
C'est beaucoup plus lent, mais les proportions sont à peu près les mêmes ...