W napięciu występują dwie „siły”: wydajność vs. czytelność.
Najpierw jednak zajmiemy się trzecim problemem, długimi liniami:
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.");
Najlepszym sposobem na wdrożenie tego i zachowanie czytelności jest użycie konkatenacji ciągów znaków:
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.");
Łączenie ze stałym ciągiem nastąpi w czasie kompilacji i nie będzie miało żadnego wpływu na wydajność. Linie są czytelne i możesz po prostu przejść dalej.
Teraz o:
System.out.println("Good morning.");
System.out.println("Please enter your name");
vs.
System.out.println("Good morning.\nPlease enter your name");
Druga opcja jest znacznie szybsza. Zasugeruję około 2 razy tak szybko .... dlaczego?
Ponieważ 90% (z szerokim marginesem błędu) pracy nie jest związane z zrzucaniem znaków na dane wyjściowe, ale jest narzucone, aby zabezpieczyć dane wyjściowe przed ich zapisaniem.
Synchronizacja
System.out
jest PrintStream
. Wszystkie implementacje Java, które znam, wewnętrznie synchronizują PrintStream: zobacz kod na GrepCode! .
Co to oznacza dla twojego kodu?
Oznacza to, że za każdym razem, gdy dzwonisz System.out.println(...)
, synchronizujesz model pamięci, sprawdzasz i czekasz na blokadę. Wszelkie inne wątki wywołujące System.out również zostaną zablokowane.
W aplikacjach jednowątkowych wpływ System.out.println()
jest często ograniczony przez wydajność IO twojego systemu, jak szybko możesz pisać do pliku. W aplikacjach wielowątkowych blokowanie może być większym problemem niż we / wy.
Rumienić się
Każdy println jest opróżniany . Powoduje to wyczyszczenie buforów i wyzwala zapis na poziomie konsoli do buforów. Ilość włożonego tutaj wysiłku zależy od implementacji, ale ogólnie rozumie się, że wydajność opróżniania jest tylko w niewielkiej części związana z wielkością opróżnianego bufora. Istnieje znaczny narzut związany z opróżnianiem, gdzie bufory pamięci są oznaczone jako brudne, maszyna wirtualna wykonuje operacje we / wy itd. Poniesienie tego obciążenia raz, a nie dwa razy, jest oczywistą optymalizacją.
Niektóre liczby
Złożyłem następujący mały test:
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);
}
}
Kod jest względnie prosty, wielokrotnie wypisuje krótki lub długi ciąg znaków do wydrukowania. Długi łańcuch zawiera wiele nowych linii. Mierzy, ile czasu zajmuje wydrukowanie 1000 iteracji każdego z nich.
Jeśli uruchomię go w wierszu polecenia unix (Linux) i przekieruję STDOUT
do /dev/null
i wydrukuję rzeczywiste wyniki STDERR
, mogę wykonać następujące czynności:
java -cp . ConsolePerf > /dev/null 2> ../errlog
Dane wyjściowe (w dzienniku błędów) wyglądają następująco:
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
Co to znaczy? Powtórzę ostatnią zwrotkę:
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
Oznacza to, że dla wszystkich celów i celów, mimo że „długa” linia jest około 5-krotnie dłuższa i zawiera wiele nowych linii, wydrukowanie jest tak samo długie jak krótkiej linii.
W długim okresie liczba znaków na sekundę jest 5 razy większa, a upływający czas jest mniej więcej taki sam .....
Innymi słowy, wydajność skaluje się w stosunku do liczby wydruków, które masz, a nie tego , co drukują.
Aktualizacja: Co się stanie, jeśli przekierujesz do pliku zamiast do / 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
Jest o wiele wolniejszy, ale proporcje są prawie takie same ....