In diesem Spannungsfeld gibt es zwei „Kräfte“: Leistung vs. Lesbarkeit.
Lassen Sie uns zuerst das dritte Problem angehen, lange Schlangen:
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.");
Die beste Möglichkeit, dies zu implementieren und die Lesbarkeit zu erhalten, ist die Verwendung der Zeichenfolgenverkettung:
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.");
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:
System.out.println("Good morning.");
System.out.println("Please enter your name");
gegen
System.out.println("Good morning.\nPlease enter your name");
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 ein PrintStream
. 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:
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);
}
}
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 Ergebnisse STDERR
, kann ich folgendes tun:
java -cp . ConsolePerf > /dev/null 2> ../errlog
Die Ausgabe (im Fehlerprotokoll) sieht folgendermaßen aus:
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
Was bedeutet das? Lassen Sie mich die letzte Strophe wiederholen:
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
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?
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
Es ist viel langsamer, aber die Proportionen sind ungefähr gleich ....