Je sais que la sortie vers la console est une opération coûteuse. Dans l'intérêt de la lisibilité du code, il est parfois agréable d'appeler une fonction pour sortir du texte deux fois, plutôt que d'avoir une longue chaîne de texte comme argument.
Par exemple, combien est-il moins efficace d'avoir
System.out.println("Good morning.");
System.out.println("Please enter your name");
contre.
System.out.println("Good morning.\nPlease enter your name");
Dans l'exemple, la différence est d'un seul appel, println()
mais que faire si c'est plus?
Sur une note connexe, les instructions impliquant l'impression de texte peuvent sembler étranges lors de l'affichage du code source si le texte à imprimer est long. En supposant que le texte lui-même ne peut pas être raccourci, que peut-on faire? Devrait-il s'agir d'un cas où plusieurs println()
appels devraient être effectués? Quelqu'un m'a dit une fois qu'une ligne de code ne devrait pas contenir plus de 80 caractères (IIRC) alors que feriez-vous avec
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.");
Est-ce la même chose pour des langages tels que C / C ++ puisque chaque fois que des données sont écrites dans un flux de sortie, un appel système doit être effectué et le processus doit passer en mode noyau (ce qui est très coûteux)?
Réponses:
Il y a ici deux «forces» en tension: performances vs lisibilité.
Abordons d'abord le troisième problème, les longues files d'attente:
La meilleure façon d'implémenter cela et de garder la lisibilité, est d'utiliser la concaténation de chaînes:
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:
contre.
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 unPrintStream
. 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:
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éelsSTDERR
, je peux faire ce qui suit:La sortie (dans errlog) ressemble à:
Qu'est-ce que ça veut dire? Permettez-moi de répéter la dernière «strophe»:
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?
C'est beaucoup plus lent, mais les proportions sont à peu près les mêmes ...
la source
"\n"
n'est peut-être pas le terminateur de ligne droit.println
terminera automatiquement la ligne avec le (s) bon (s) caractère (s), mais coller\n
directement un dans votre chaîne peut provoquer des problèmes. Si vous voulez le faire correctement, vous devrez peut-être utiliser la mise en forme des chaînes ou laline.separator
propriété système .println
est beaucoup plus propre.Je ne pense pas que le fait d'avoir un tas de
println
s soit un problème de conception. Selon moi, cela peut clairement être fait avec un analyseur de code statique si c'est vraiment un problème.Mais ce n'est pas un problème car la plupart des gens ne font pas des IO comme ça. Quand ils ont vraiment besoin de faire beaucoup d'E / S, ils utilisent des tampons (BufferedReader, BufferedWriter, etc.) lorsque l'entrée est tamponnée, vous verrez que les performances sont assez similaires, que vous n'avez pas à vous soucier d'avoir un tas
println
ou peuprintln
.Donc, pour répondre à la question d'origine. Je dirais, pas mal si vous utilisez
println
pour imprimer quelques choses comme la plupart des gens le feraientprintln
.la source
Dans les langages de niveau supérieur comme C et C ++, c'est moins un problème qu'en Java.
Tout d'abord, C et C ++ définissent la concaténation de chaînes au moment de la compilation, vous pouvez donc quelque chose comme:
Dans un tel cas, la concaténation de la chaîne n'est pas seulement une optimisation que vous pouvez à peu près, généralement (etc.) dépendre du compilateur à faire. Il est plutôt directement requis par les normes C et C ++ (phase 6 de la traduction: "Les jetons littéraux de chaîne adjacents sont concaténés.").
Bien que cela se fasse au détriment d'un peu plus de complexité dans le compilateur et l'implémentation, C et C ++ font un peu plus pour cacher la complexité de la production efficace de sortie du programmeur. Java ressemble beaucoup plus au langage d'assemblage - chaque appel à se
System.out.println
traduit beaucoup plus directement par un appel à l'exploitation sous-jacente pour écrire les données sur la console. Si vous souhaitez que la mise en mémoire tampon améliore l'efficacité, cela doit être fourni séparément.Cela signifie, par exemple, qu'en C ++, réécrivant l'exemple précédent, à quelque chose comme ceci:
... normalement 1 ont presque aucun effet sur l' efficacité. Chaque utilisation de
cout
déposerait simplement des données dans un tampon. Ce tampon était vidé dans le flux sous-jacent lorsque le tampon était rempli, ou que le code essayait de lire l'entrée de l'utilisation (comme avecstd::cin
).iostream
Les s ont également unesync_with_stdio
propriété qui détermine si la sortie des iostreams est synchronisée avec l'entrée de style C (par exemple,getchar
). Par défaut, la valeursync_with_stdio
est true, donc si, par exemple, vous écrivezstd::cout
, puis lisez viagetchar
, les données que vous avez écritescout
seront vidées lors de l'getchar
appel. Vous pouvez définirsync_with_stdio
false pour désactiver cela (généralement effectué pour améliorer les performances).sync_with_stdio
contrôle également un degré de synchronisation entre les threads. Si la synchronisation est activée (par défaut), l'écriture sur un iostream à partir de plusieurs threads peut entraîner l'entrelacement des données des threads, mais empêche toute condition de concurrence critique. IOW, votre programme s'exécutera et produira une sortie, mais si plusieurs threads écrivent dans un flux à la fois, le mélange arbitraire des données des différents threads rendra généralement la sortie assez inutile.Si vous désactivez la synchronisation, la synchronisation de l'accès à partir de plusieurs threads devient également votre entière responsabilité. Les écritures simultanées à partir de plusieurs threads peuvent / entraîneront une course aux données, ce qui signifie que le code a un comportement indéfini.
Sommaire
Par défaut, C ++ tente de concilier vitesse et sécurité. Le résultat est assez réussi pour le code à un seul thread, mais moins pour le code à plusieurs threads. Le code multithread doit généralement garantir qu'un seul thread écrit dans un flux à la fois pour produire une sortie utile.
1. Il est possible de désactiver la mise en mémoire tampon pour un flux, mais en fait, cela est assez inhabituel, et quand / si quelqu'un le fait, c'est probablement pour une raison assez spécifique, comme s'assurer que toutes les sorties sont capturées immédiatement malgré l'effet sur les performances . Dans tous les cas, cela ne se produit que si le code le fait explicitement.
la source
"2^31 - 1 = " + Integer.MAX_VALUE
est stocké sous la forme d'une seule chaîne interne (JLS Sec 3.10.5 et 15.28 ).Bien que les performances ne soient pas vraiment un problème ici, la mauvaise lisibilité d'un tas de
println
déclarations pointe vers un aspect de conception manquant.Pourquoi écrivons-nous une séquence de nombreuses
println
déclarations? S'il ne s'agissait que d'un bloc de texte fixe, comme un--help
texte dans une commande de console, il serait préférable de l'avoir comme ressource distincte et de le lire et de l'écrire à l'écran sur demande.Mais généralement, c'est un mélange de parties dynamiques et statiques. Disons que nous avons des données de commande nues d'une part, et des parties de texte statique fixes d'autre part, et ces choses doivent être mélangées pour former une feuille de confirmation de commande. Encore une fois, dans ce cas également, il est préférable d'avoir un fichier texte de ressource distinct: La ressource serait un modèle, contenant une sorte de symboles (espaces réservés), qui sont remplacés au moment de l'exécution par les données de commande réelles.
La séparation du langage de programmation du langage naturel présente de nombreux avantages, parmi lesquels l'internationalisation: il se peut que vous deviez traduire le texte si vous souhaitez devenir multilingue avec votre logiciel. En outre, pourquoi une étape de compilation devrait-elle être nécessaire si vous souhaitez uniquement avoir une correction textuelle, par exemple corriger une faute d'orthographe.
la source