Musimy cały czas budować ciągi dla danych wyjściowych dziennika i tak dalej. W wersjach JDK nauczyliśmy się, kiedy używać StringBuffer
(wiele dodatków, bezpieczny wątek) i StringBuilder
(wiele dodatków, nie bezpieczny wątek).
Jaka jest rada na temat korzystania String.format()
? Czy jest wydajny, czy też jesteśmy zmuszeni trzymać się konkatenacji dla jedno-liniowych, w których wydajność jest ważna?
np. brzydki stary styl,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
vs. schludny nowy styl (String.format, który jest prawdopodobnie wolniejszy),
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
Uwaga: moim szczególnym przypadkiem użycia są setki ciągów dziennika „jeden wiersz” w całym kodzie. Nie wymagają pętli, więc StringBuilder
jest zbyt ciężki. Interesuje mnie String.format()
konkretnie.
Odpowiedzi:
Napisałem małą klasę do przetestowania, która ma lepszą wydajność dwóch i + wyprzedza format. od 5 do 6 razy. Spróbuj sam
Wykonanie powyższego dla różnych N pokazuje, że oba zachowują się liniowo, ale
String.format
są 5-30 razy wolniejsze.Powodem jest to, że w bieżącej implementacji
String.format
najpierw analizuje dane wejściowe za pomocą wyrażeń regularnych, a następnie wypełnia parametry. Z drugiej strony konkatenacja z plusem jest optymalizowana przez javac (nie przez JIT) i używanaStringBuilder.append
bezpośrednio.źródło
Wziąłem hhafez kod i dodał test pamięci :
Uruchamiam to osobno dla każdego podejścia, operatora „+”, String.format i StringBuilder (wzywając toString ()), więc inne podejścia nie będą miały wpływu na używaną pamięć. Dodałem więcej konkatenacji, tworząc ciąg jako „Blah” + i + „Blah” + i + „Blah” + i + „Blah”.
Rezultaty są następujące (średnio po 5 przebiegów):
Czas podejścia (ms) Przydzielona pamięć (długa)
operator „+” 747 320,504
String.format 16484 373,312
StringBuilder 769 57 344
Widzimy, że String „+” i StringBuilder są praktycznie identyczne pod względem czasu, ale StringBuilder jest znacznie wydajniejszy w użyciu pamięci. Jest to bardzo ważne, gdy mamy wiele wywołań dziennika (lub dowolnych innych instrukcji zawierających łańcuchy) w odstępie czasu wystarczająco krótkim, aby Garbage Collector nie był w stanie wyczyścić wielu instancji łańcucha wynikającego z operatora „+”.
I uwaga, BTW, nie zapomnij sprawdzić poziomu rejestrowania przed zbudowaniem wiadomości.
Wnioski:
źródło
+
operatora kompiluje do równoważnegoStringBuilder
kodu. Mikrobenszety takie jak ten nie są dobrym sposobem mierzenia wydajności - dlaczego nie użyć jvisualvm, jest z jakiegoś powodu w jdk.String.format()
będzie wolniejszy, ale ze względu na czas, aby przeanalizować ciąg formatu, a nie przydziały obiektów. Odroczenie tworzenia artefaktów rejestrowania, dopóki nie upewnisz się, że są one potrzebne, jest dobrą radą, ale jeśli miałoby to wpływ na wydajność, jest w niewłaściwym miejscu.And a note, BTW, don't forget to check the logging level before constructing the message.
nie jest dobra rada. Zakładając, że mówimyjava.util.logging.*
konkretnie, sprawdzanie poziomu rejestrowania ma miejsce, gdy mówisz o zaawansowanym przetwarzaniu, które spowodowałoby niekorzystne skutki dla programu, którego nie chciałbyś, gdy program nie ma włączonego rejestrowania na odpowiednim poziomie. Formatowanie łańcucha nie jest wcale tego rodzaju przetwarzaniem. Formatowanie jest częściąjava.util.logging
frameworka, a sam program rejestrujący sprawdza poziom rejestrowania przed wywołaniem formatera.Wszystkie przedstawione tutaj testy porównawcze mają pewne wady , dlatego wyniki nie są wiarygodne.
Byłem zaskoczony, że nikt nie używał JMH do testów porównawczych, więc zrobiłem to.
Wyniki:
Jednostki to operacje na sekundę, im więcej, tym lepiej. Kod źródłowy testu porównawczego . Użyto wirtualnej maszyny Java OpenJDK IcedTea 2.5.4.
Tak więc stary styl (używanie +) jest znacznie szybszy.
źródło
Twój stary brzydki styl jest automatycznie kompilowany przez JAVAC 1.6 jako:
Więc nie ma absolutnie żadnej różnicy między tym a użyciem StringBuilder.
String.format ma dużo większą wagę, ponieważ tworzy nowy formatter, analizuje ciąg formatu wejściowego, tworzy StringBuilder, dołącza do niego wszystko i wywołuje metodęString ().
źródło
+
iStringBuilder
rzeczywiście. Niestety w innych odpowiedziach w tym wątku jest wiele dezinformacji. Niemal mam ochotę zmienić pytanie nahow should I not be measuring performance
.String.format Java działa tak:
jeśli ostatecznym miejscem docelowym dla tych danych jest strumień (np. renderowanie strony internetowej lub zapisywanie do pliku), możesz złożyć fragmenty formatu bezpośrednio w strumień:
Spekuluję, że optymalizator zoptymalizuje przetwarzanie ciągu formatu. Jeśli tak, pozostaje Ci tyle samo zamortyzowanej wydajności, co ręczne rozwijanie pliku String.format w StringBuilder.
źródło
String.format
w wewnętrznych pętlach (uruchamianie milionów razy) skutkowało ponad 10% mojego czasu wykonywaniajava.util.Formatter.parse(String)
. Wydaje się to wskazywać, że w wewnętrznych pętlach należy unikać wywoływaniaFormatter.format
lub czegokolwiek, co go wywołuje, w tymPrintStream.format
(wady standardowej biblioteki Java JO, IMO, zwłaszcza, że nie można buforować analizowanego ciągu formatu).Aby rozwinąć / poprawić pierwszą odpowiedź powyżej, nie jest to tłumaczenie, w którym String.format faktycznie by pomógł.
Pomaga w tym String.format, kiedy drukujesz datę / godzinę (lub format numeryczny itp.), Gdzie występują różnice lokalizacyjne (1010) (tj. Niektóre kraje wydrukują 04 lutego 2009, a inne wydrukują 04 lutego 2009).
W tłumaczeniu mówisz tylko o przeniesieniu dowolnych eksternalizowalnych ciągów (takich jak komunikaty o błędach i nie-to) do pakietu właściwości, abyś mógł użyć odpowiedniego pakietu dla odpowiedniego języka, używając ResourceBundle i MessageFormat.
Patrząc na wszystkie powyższe, powiedziałbym, że pod względem wydajności String.format vs. zwykła konkatenacja sprowadza się do tego, co wolisz. Jeśli wolisz patrzeć na wywołania .format zamiast konkatenacji, to na pewno idź z tym.
W końcu kod jest odczytywany znacznie więcej niż jest napisany.
źródło
W twoim przykładzie wydajność prawdopodobnie nie różni się zbytnio, ale należy wziąć pod uwagę inne kwestie: a mianowicie fragmentację pamięci. Nawet operacja konkatenacji tworzy nowy ciąg, nawet jeśli jest tymczasowy (potrzeba czasu, aby go GC i więcej pracy). String.format () jest po prostu bardziej czytelny i wymaga mniejszej fragmentacji.
Ponadto, jeśli często używasz określonego formatu, nie zapomnij, że możesz bezpośrednio użyć klasy Formatter () (wszystko, co robi String.format (), tworzy jedną instancję Formattera).
Ponadto należy pamiętać o czymś innym: należy uważać na użycie substring (). Na przykład:
Ten duży ciąg jest nadal w pamięci, ponieważ tak właśnie działają podciągi Java. Lepsza wersja to:
lub
Druga forma jest prawdopodobnie bardziej przydatna, jeśli robisz inne rzeczy w tym samym czasie.
źródło
Ogólnie powinieneś używać String.Format, ponieważ jest stosunkowo szybki i obsługuje globalizację (zakładając, że faktycznie próbujesz napisać coś, co jest czytane przez użytkownika). Ułatwia także globalizację, jeśli próbujesz przetłumaczyć jeden ciąg w porównaniu do 3 lub więcej na instrukcję (szczególnie dla języków, które mają drastycznie różne struktury gramatyczne).
Teraz, jeśli nigdy nie planujesz niczego tłumaczyć, polegaj na wbudowanej w Javę konwersji operatorów + na język
StringBuilder
. Lub użyjStringBuilder
jawnie Java .źródło
Inna perspektywa tylko z punktu widzenia rejestrowania.
Widzę wiele dyskusji związanych z logowaniem się do tego wątku, więc pomyślałem o dodaniu mojego doświadczenia w odpowiedzi. Być może ktoś uzna to za przydatne.
Wydaje mi się, że motywacją do logowania przy użyciu formatera jest unikanie łączenia łańcuchów. Zasadniczo, nie chcesz mieć narzutu łańcucha konkat, jeśli nie zamierzasz go zalogować.
Tak naprawdę nie musisz konkatować / formatować, chyba że chcesz się zalogować. Powiedzmy, że jeśli zdefiniuję taką metodę
W tym podejściu cancat / formatter nie jest tak naprawdę wywoływany, jeśli jest to komunikat debugowania, a debugOn = false
Chociaż nadal lepiej będzie użyć StringBuilder zamiast formatera. Główną motywacją jest unikanie tego.
Jednocześnie od tego czasu nie lubię dodawać bloku „if” do każdej instrukcji logowania
Dlatego wolę utworzyć klasę narzędzia do rejestrowania za pomocą metod takich jak powyżej i używać jej wszędzie, nie martwiąc się o obniżenie wydajności i inne związane z tym problemy.
źródło
Właśnie zmodyfikowałem test hhafeza, aby uwzględnić StringBuilder. StringBuilder jest 33 razy szybszy niż String.format przy użyciu klienta jdk 1.6.0_10 na XP. Użycie przełącznika -server obniża współczynnik do 20.
Chociaż może to zabrzmieć drastycznie, uważam, że ma to znaczenie tylko w rzadkich przypadkach, ponieważ liczby bezwzględne są dość niskie: 4 s na 1 milion prostych wywołań String.format jest w porządku - o ile używam ich do logowania lub lubić.
Aktualizacja: Jak wskazał sjbotha w komentarzach, test StringBuilder jest nieprawidłowy, ponieważ brakuje w nim wersji końcowej
.toString()
.Prawidłowy współczynnik przyspieszenia od
String.format(.)
doStringBuilder
wynosi 23 na mojej maszynie (16 z-server
przełącznikiem).źródło
Oto zmodyfikowana wersja wpisu hhafez. Zawiera opcję konstruktora ciągów.
}
Czas po pętli 391 Czas po pętli 4163 Czas po pętli 227
źródło
Odpowiedź zależy w dużej mierze od tego, w jaki sposób Twój kompilator Java optymalizuje generowany kod bajtowy. Ciągi są niezmienne i teoretycznie każda operacja „+” może utworzyć nową. Ale twój kompilator prawie na pewno optymalizuje tymczasowe kroki w budowaniu długich łańcuchów. Jest całkiem możliwe, że oba powyższe wiersze kodu generują dokładnie ten sam kod bajtowy.
Jedynym prawdziwym sposobem na sprawdzenie tego jest iteracyjne przetestowanie kodu w bieżącym środowisku. Napisz aplikację QD, która łączy ciągi w obie strony iteracyjnie, i zobacz, jak się ze sobą łączą.
źródło
Rozważ użycie
"hello".concat( "world!" )
niewielkiej liczby ciągów w konkatenacji. Wydajność może być jeszcze lepsza niż w przypadku innych podejść.Jeśli masz więcej niż 3 łańcuchy, rozważ użycie StringBuilder lub po prostu String, w zależności od używanego kompilatora.
źródło