Ich weiß, dass die Ausgabe an die Konsole ein kostspieliger Vorgang ist. Im Interesse der Lesbarkeit von Code ist es manchmal hilfreich, eine Funktion aufzurufen, um Text zweimal auszugeben, anstatt eine lange Zeichenfolge als Argument zu verwenden.
Zum Beispiel, wie viel weniger effizient ist es zu haben
System.out.println("Good morning.");
System.out.println("Please enter your name");
gegen
System.out.println("Good morning.\nPlease enter your name");
Im Beispiel ist der Unterschied nur ein Anruf, println()
aber was ist, wenn es mehr ist?
In einem verwandten Hinweis können Aussagen, die das Drucken von Text betreffen, beim Anzeigen des Quellcodes merkwürdig aussehen, wenn der zu druckende Text lang ist. Angenommen, der Text selbst kann nicht gekürzt werden. Was kann getan werden? Sollte dies ein Fall sein, in dem mehrere println()
Anrufe getätigt werden? Jemand sagte mir einmal, eine Codezeile sollte nicht mehr als 80 Zeichen (IIRC) enthalten. Was würden Sie also tun?
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.");
Gilt das auch für Sprachen wie C / C ++, da jedes Mal, wenn Daten in einen Ausgabestream geschrieben werden, ein Systemaufruf erfolgen muss und der Prozess in den Kernelmodus wechseln muss (was sehr kostspielig ist)?
Antworten:
In diesem Spannungsfeld gibt es zwei „Kräfte“: Leistung vs. Lesbarkeit.
Lassen Sie uns zuerst das dritte Problem angehen, lange Schlangen:
Die beste Möglichkeit, dies zu implementieren und die Lesbarkeit zu erhalten, ist die Verwendung der Zeichenfolgenverkettung:
Die Verkettung der Zeichenfolgenkonstanten erfolgt zur Kompilierungszeit und hat keinerlei Auswirkungen auf die Leistung. Die Zeilen sind lesbar und Sie können einfach weitermachen.
Nun zu den:
gegen
Die zweite Option ist deutlich schneller. Ich werde etwa 2X so schnell vorschlagen .... warum?
Denn 90% (mit einer großen Fehlerquote) der Arbeit beziehen sich nicht auf das Ausgeben der Zeichen in die Ausgabe, sondern auf den Aufwand, der erforderlich ist, um die Ausgabe für das Schreiben in die Ausgabe zu sichern.
Synchronisation
System.out
ist einPrintStream
. Alle mir bekannten Java-Implementierungen synchronisieren den PrintStream intern: Siehe den Code auf GrepCode! .Was bedeutet das für Ihren Code?
Dies bedeutet, dass Sie jedes Mal, wenn
System.out.println(...)
Sie Ihr Speichermodell synchronisieren, überprüfen und auf eine Sperre warten. Alle anderen Threads, die System.out aufrufen, werden ebenfalls gesperrt.In Single-Thread-Anwendungen wird die Auswirkung
System.out.println()
häufig durch die E / A-Leistung Ihres Systems begrenzt. Wie schnell können Sie in eine Datei schreiben? In Multithread-Anwendungen kann das Sperren ein größeres Problem darstellen als die E / A.Spülen
Jeder Druck wird geleert . Dies bewirkt, dass die Puffer gelöscht werden, und löst ein Schreiben auf Konsolenebene in die Puffer aus. Der Aufwand, der hier unternommen wird, hängt von der Implementierung ab, es versteht sich jedoch im Allgemeinen, dass die Leistung des Spülvorgangs nur zu einem kleinen Teil mit der Größe des zu spülenden Puffers zusammenhängt. Mit dem Leeren ist ein erheblicher Aufwand verbunden, da Speicherpuffer als fehlerhaft markiert sind, die virtuelle Maschine E / A-Vorgänge ausführt usw. Es ist eine offensichtliche Optimierung, diesen Aufwand einmal statt zweimal zu verursachen.
Einige Zahlen
Ich habe folgenden kleinen Test zusammengestellt:
Der Code ist relativ einfach und gibt wiederholt entweder eine kurze oder eine lange Zeichenfolge aus, die ausgegeben werden soll. Die lange Zeichenfolge enthält mehrere Zeilenumbrüche. Es misst, wie lange es dauert, jeweils 1000 Iterationen zu drucken.
Wenn ich es auf dem Unix (Linux) Befehlszeile ausführen, und leiten das
STDOUT
zu/dev/null
, und drucken Sie die tatsächlichen ErgebnisseSTDERR
, kann ich folgendes tun:Die Ausgabe (im Fehlerprotokoll) sieht folgendermaßen aus:
Was bedeutet das? Lassen Sie mich die letzte Strophe wiederholen:
Dies bedeutet, dass die Ausgabe in jeder Hinsicht genauso lange dauert wie die Ausgabe der kurzen Zeile, obwohl die lange Zeile etwa fünfmal länger ist und mehrere neue Zeilen enthält.
Die Anzahl der Zeichen pro Sekunde ist auf lange Sicht fünfmal so hoch und die verstrichene Zeit ungefähr gleich.
Mit anderen Worten, skaliert Ihre Leistung in Bezug auf die Anzahl von printlns Sie haben, nicht , was sie drucken.
Update: Was passiert, wenn Sie auf eine Datei anstatt auf / dev / null umleiten?
Es ist viel langsamer, aber die Proportionen sind ungefähr gleich ....
quelle
"\n"
möglicherweise nicht der richtige Zeilenabschluss ist.println
schließt die Zeile automatisch mit den richtigen Zeichen ab, das\n
direkte Einfügen von a in die Zeichenfolge kann jedoch zu Problemen führen. Wenn Sie es richtig machen möchten, müssen Sie möglicherweise die Zeichenfolgenformatierung oder dieline.separator
Systemeigenschaft verwenden .println
ist viel sauberer.Ich denke nicht, dass ein Haufen
println
s überhaupt ein Designproblem ist. Ich sehe es so, dass dies mit dem Static Code Analyzer eindeutig möglich ist, wenn es wirklich ein Problem ist.Dies ist jedoch kein Problem, da die meisten Benutzer solche E / A-Vorgänge nicht ausführen. Wenn sie wirklich viele E / A-Vorgänge ausführen müssen, verwenden sie gepufferte Vorgänge (BufferedReader, BufferedWriter usw.). Wenn die Eingabe gepuffert ist, werden Sie feststellen, dass die Leistung ähnlich genug ist und Sie sich keine Sorgen um eine machen müssen Haufen
println
oder wenigeprintln
.Also, um die ursprüngliche Frage zu beantworten. Ich würde sagen, nicht schlecht, wenn Sie
println
ein paar Dinge ausdrucken, für die die meisten Leute verwenden würdenprintln
.quelle
In höheren Sprachen wie C und C ++ ist dies weniger problematisch als in Java.
Zunächst definieren C und C ++ die Verkettung von Zeichenfolgen zur Kompilierungszeit, sodass Sie so etwas wie Folgendes tun können:
In einem solchen Fall ist das Verketten der Zeichenfolge nicht nur eine Optimierung, die Sie in der Regel (usw.) vom Compiler abhängig machen können. Vielmehr wird es direkt von den C- und C ++ - Standards gefordert (Phase 6 der Übersetzung: "Angrenzende Zeichenfolgen-Literal-Token werden verkettet.").
Obwohl C und C ++ den Compiler und die Implementierung etwas komplizierter machen, tragen sie etwas mehr dazu bei, die Komplexität der effizienten Ausgabe vor dem Programmierer zu verbergen. Java ist viel mehr eine Assemblersprache - jeder Aufruf wird
System.out.println
direkter in einen Aufruf des zugrunde liegenden Betriebs übersetzt, um die Daten in die Konsole zu schreiben. Wenn die Effizienz durch Pufferung verbessert werden soll, muss dies separat angegeben werden.Dies bedeutet zum Beispiel, dass in C ++ das vorherige Beispiel folgendermaßen umgeschrieben wird:
... normalerweise 1 haben fast keinen Einfluss auf die Effizienz. Jede Verwendung von
cout
würde einfach Daten in einem Puffer ablegen. Dieser Puffer würde in den zugrunde liegenden Stream gespült, wenn der Puffer voll ist oder der Code versucht, Eingaben von der Verwendung (wie z. B. mitstd::cin
) zu lesen .iostream
s haben auch einesync_with_stdio
Eigenschaft, die bestimmt, ob die Ausgabe von iostreams mit der Eingabe im C-Stil synchronisiert wird (zgetchar
. B. ). Standardmäßigsync_with_stdio
ist true festgelegt. Wenn Sie also beispielsweise schreibenstd::cout
und dann über lesengetchar
, werden die Daten, in die Sie geschrieben haben,cout
beim Aufrufen gelöschtgetchar
. Sie können den Wertsync_with_stdio
auf false setzen, um dies zu deaktivieren (normalerweise, um die Leistung zu verbessern).sync_with_stdio
Steuert auch den Synchronisationsgrad zwischen Threads. Wenn die Synchronisierung aktiviert ist (Standardeinstellung), kann das Schreiben von mehreren Threads in einen Iostream dazu führen, dass die Daten aus den Threads verschachtelt werden, verhindert jedoch alle Race-Bedingungen. IOW, Ihr Programm wird ausgeführt und erzeugt eine Ausgabe, aber wenn mehr als ein Thread gleichzeitig in einen Stream schreibt, macht das willkürliche Vermischen der Daten aus den verschiedenen Threads die Ausgabe normalerweise ziemlich nutzlos.Wenn Sie die Option aus der Synchronisation, dann synchronisieren Zugriff von mehreren Threads wird vollständig in Ihrer Verantwortung als gut. Gleichzeitiges Schreiben von mehreren Threads kann / wird zu einem Datenwettlauf führen, was bedeutet, dass der Code ein undefiniertes Verhalten aufweist.
Zusammenfassung
In C ++ wird standardmäßig versucht, Geschwindigkeit und Sicherheit in Einklang zu bringen. Das Ergebnis ist für Code mit einem Thread ziemlich erfolgreich, für Code mit mehreren Threads jedoch weniger. Multithread-Code muss normalerweise sicherstellen, dass jeweils nur ein Thread in einen Stream schreibt, um eine nützliche Ausgabe zu erzielen.
1. Es ist möglich, die Pufferung für einen Stream zu deaktivieren, dies ist jedoch ziemlich ungewöhnlich, und wenn / wenn jemand dies tut, liegt dies wahrscheinlich an einem bestimmten Grund, z . In jedem Fall geschieht dies nur, wenn der Code dies explizit tut.
quelle
"2^31 - 1 = " + Integer.MAX_VALUE
als einzelne internierte Zeichenfolge gespeichert (JLS Sec 3.10.5 und 15.28 ).Während die Leistung hier nicht wirklich von Bedeutung ist
println
, deutet die schlechte Lesbarkeit einiger Aussagen auf einen fehlenden Designaspekt hin.Warum schreiben wir eine Folge vieler
println
Aussagen? Wenn es nur ein fester Textblock wäre, wie ein--help
Text in einem Konsolenbefehl, wäre es viel besser, ihn als separate Ressource zu haben und ihn auf Anfrage einzulesen und auf den Bildschirm zu schreiben.In der Regel handelt es sich jedoch um eine Mischung aus dynamischen und statischen Teilen. Nehmen wir an, wir haben einerseits einige reine Bestelldaten und andererseits einige feste statische Textteile, und diese Dinge müssen zusammengemischt werden, um ein Bestellbestätigungsblatt zu bilden. Auch in diesem Fall ist es besser, eine separate Ressourcentextdatei zu haben: Die Ressource wäre eine Vorlage, die eine Art von Symbolen (Platzhaltern) enthält, die zur Laufzeit durch die eigentlichen Auftragsdaten ersetzt werden.
Die Trennung der Programmiersprache von der natürlichen Sprache hat viele Vorteile - darunter die Internationalisierung: Möglicherweise müssen Sie den Text übersetzen, wenn Sie mit Ihrer Software mehrsprachig werden möchten. Warum sollte ein Kompilierungsschritt auch notwendig sein, wenn Sie nur eine Textkorrektur wünschen, beispielsweise um Rechtschreibfehler zu beheben?
quelle